ACM板子¶
重链剖分(HLD)¶
namespace HLD{
//树的常规信息---
vector<int>tree[N];
int fa[N],size[N],depth[N];//深度用于lca
//---树的常规信息
int top[N];//链头
int wson[N];//重儿子
int hld_dfn[N],dfncnt;//dfn序(一条重链和一个子树dfn序连续
void dfs1(int u,int father){//第一次dfs找到重儿子,父亲,子树重量和深度
size[u]=1;fa[u]=father;
depth[u]=depth[father]+1;
for(int v:tree[u])
{
if(v!=father){//不回父边
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[wson[u]]) wson[u]=v;//重儿子更换
}
}
}
void dfs2(int u,int father,int utop){//第二次dfs创建重链,标记链头,找到dfn序
top[u]=utop;
hld_dfn[u]=(++dfncnt);//dfs序
//.........//可添加dfs序维护的东西
if(wson[u]!=0) dfs2(wson[u],u,utop);//先遍历重儿子,链头不变
for(int v:tree[u]){
if(v!=father&&v!=wson[u]){
dfs2(v,u,v);//再遍历轻儿子,链头变为轻儿子自身
}
}
}
int lca(int u,int v){//使用HLD找到LCA//也可改为返回ans
while(top[u]!=top[v])
{
if(depth[top[u]]>depth[top[v]]) swap(u,v);//保证v的链头深度较深
//...//这里可以更新dfs序中的[hld_dfn[top[v]],hld_dfn[v]]
v=fa[top[v]];
}
if(depth[u]>depth[v]) swap(u,v);// 保证v比较深
//...//这里可以更新[hld_dfn[u],hld_dfn[v]]
return u;
}
}
树状数组¶
namespace BIT{
int bit[N];//树状数组本体
int n;//树状数组大小
int lowbit(int x) {return x&(-x);}
void update(int id,int date)//单点加
{
for(int i=id;i<=n;i+=lowbit(i)) bit[i]+=date;
}
int query(int id)//查询前缀和
{
int res=0;
for(int i=id;i>0;i-=lowbit(i)) res+=bit[i];
return res;
}
}
快速幂¶
递归写法¶
long long fast_pow(long long a,long long k){
if(k==0) return 1;
long long res=fast_pow(a,k>>1)%p;
if(k%2)
return ((res*res)%p*a)%p;
else
return (res*res)%p;
}
非递归写法¶
long long fast_pow(long long a,long long k)
{
//a为当前位对应的a的幂次
long long res=1;
while(k>0)
{
if(k&1) res=(res*a)%p;
a=(a*a)%p;
k>>=1;
}
return res;
}
memset¶
无论t是几维数组,均可以使用
memset(t,val,sizeof(t));
进行初始化,使其全部为val,例如当val=0时数组将被全部初始化为0
Note
注意:int数组只能初始化为-1或0,char数组则可以任意(有关字节存储问题)
SAM¶
注:并未完全完善
namespace SAM{
void text();
int last=0;//原串所在节点
int cnt=0;//目前开创节点
struct node
{
int son[26];
int father=-1;//link树上的
int num;
int len;
}sam[N<<1];
void newnode(int length,int number){//建立新节点满足
//cnt++;
sam[++cnt].len=length;
sam[cnt].father=-1;//先设父节点未知(建完后只有根节点0父节点未知)
sam[cnt].num=number;
memset(sam[cnt].son,0,sizeof(sam[cnt].son));
}
void insert(int c){//输入单字符(按字母表转化为数字)
newnode(sam[last].len+1,1);//建立新endpos
int now=cnt,p=last;//现在的节点编号和正在跳father的节点编号
//cout<<now<<" "<<p<<endl;
while(!sam[p].son[c]&&p!=-1)//跳father直到有这个儿子或者到头(越过0节点的头)
{
//cout<<p<<endl;
sam[p].son[c]=now;
p=sam[p].father;
}
if(p==-1)
sam[now].father=0;//cout<<"ee"<<endl;//父亲为根
else{
int sonnode=sam[p].son[c];//待拆点/处理的点
if(sam[sonnode].len==sam[p].len+1) sam[now].father=sonnode;//不用拆点
else{//拆点
//cout<<"ee";
newnode(sam[p].len+1,0);
int newson=cnt;
sam[newson].father=sam[sonnode].father;
sam[sonnode].father=newson;//父亲关系
memcpy(sam[newson].son,sam[sonnode].son,sizeof(sam[sonnode].son));//儿子关系
//前面的儿子关系
while(p!=-1&&sam[p].son[c]==sonnode)
{
sam[p].son[c]=newson;//变更关系
p=sam[p].father;
}
sonnode=newson;//便于后边统计数量
sam[now].father=newson;
}
//cout<<sonnode<<endl;
}
last=now;
}
int query(string s)//查询字符串s次数
{
int now=0,flag=1;
for(int i=0;i<s.length();i++){
int id=s[i]-'a';
if(!sam[now].son[id]) {flag=0;break;}
now=sam[now].son[id];
}
if(flag) return sam[now].num;
else return 0;
}
void add(string t){//往后插入t
for(char i:t)
{
insert(i-'a');
//cout<<sam[cnt].len-sam<<endl;
//text();
}
}
long long total()//找子串个数
{
long long res=0;
for(int i=1;i<=cnt;i++)
{ if(sam[i].num!=1)
res=max(res,1ll*sam[i].num*sam[i].len);
}return res;
}
void text(){//输出SAM
cout<<"-----------------------------------------------"<<endl;
for(int i=1;i<=cnt;i++)
{
cout<<i<<":father-"<<sam[i].father<<endl;
cout<<"len:"<<sam[i].len<<" num:"<<sam[i].num<<endl;
cout<<"son:";
for(int j=0;j<=25;j++){
if(sam[i].son[j]) cout<<j<<"-"<<sam[i].son[j]<<" ";
}
cout<<endl<<endl;
}
cout<<"-----------------------------------------------"<<endl;
}
}
AC自动机¶
给你一个文本串 \(S\) 和 \(n\) 个模式串 \(T_{1 \sim n}\),请你分别求出每个模式串 \(T_i\) 在 \(S\) 中出现的次数。
第一行包含一个正整数 \(n\) 表示模式串的个数。
接下来 \(n\) 行,第 \(i\) 行包含一个由小写英文字母构成的非空字符串 \(T_i\)。
最后一行包含一个由小写英文字母构成的非空字符串 \(S\)。
数据不保证任意两个模式串不相同。
输出包含 \(n\) 行,其中第 \(i\) 行包含一个非负整数表示 \(T_i\) 在 \(S\) 中出现的次数。
对于 \(100 \%\) 的数据,\(1 \le n \le 2 \times {10}^5\),\(T_{1 \sim n}\) 的长度总和不超过 \(2 \times {10}^5\),\(S\) 的长度不超过 \(2 \times {10}^6\)。
#include<bits/stdc++.h>
#define N 1145141
using namespace std;
namespace AC{//对应P5357即求各串出现次数的统计
int cnt=0;//动态开点
int total[N];//对于串出现次数的统计
struct node{
int ch[26];
int fail=0;
int num=0;
vector<int>end;//注意可能有重复的字符串
}actree[N];//ac自动机本体
int inq[N];
void newnode(){
actree[++cnt].fail=0;
actree[cnt].end.clear();
inq[cnt]=0;
memset(actree[cnt].ch,0,sizeof(actree[cnt].ch));
}
void insert(string s,int id)//加入一个字符串
{
int now=0;
for(int i=0;i<s.length();i++)
{
int id=s[i]-'a';
if(!actree[now].ch[id]) {
newnode();
actree[now].ch[id]=cnt;
}
now=actree[now].ch[id];
}
actree[now].end.push_back(id);
}
void bfs_fail()//bfs求fail指针
{
queue<int>BFS;
for(int i=0;i<26;i++) if(actree[0].ch[i]) BFS.push(actree[0].ch[i]);
while(!BFS.empty())
{
int now=BFS.front();
//cout<<now;
BFS.pop();
for(int i=0;i<26;i++)
{
if(actree[now].ch[i]){//有了话fail为父亲的fail后继并且入队
actree[actree[now].ch[i]].fail=actree[actree[now].fail].ch[i];
inq[actree[actree[now].fail].ch[i]]++;
BFS.push(actree[now].ch[i]);
}
else{//无了话儿子就是父亲的fail后继
actree[now].ch[i]=actree[actree[now].fail].ch[i];
}
}
}
}
int query(string s){//询问有多少种字串出现
int res=0;
int now=0;
for(int i=0;i<s.length();i++)
{
int id=s[i]-'a';
now=actree[now].ch[id];
actree[now].num++;
}
return res;
}
void init()//初始化AC自动机
{
cnt=-1;
newnode();
cnt=0;
}
void topo(){//拓扑排序求total
queue<int>BFS;
for(int i=1;i<=cnt;i++)
{
if(inq[i]==0){
BFS.push(i);
}
}
while(!BFS.empty()){
int now=BFS.front();BFS.pop();
for(int id:actree[now].end) total[id]+=actree[now].num;
int nxt=actree[now].fail;
if(nxt!=0){
actree[nxt].num+=actree[now].num;
inq[nxt]--;
if(inq[nxt]==0) BFS.push(nxt);
}
}
}
}
int n;
string s[214514],t="";
int main()
{
AC::init();
cin>>n;
for(int i=1;i<=n;i++){
cin>>s[i];
AC::insert(s[i],i);t+='&';
t+=s[i];
AC::total[i]=0;
//cout<<"kk";
}
AC::bfs_fail();
AC::query(t);
AC::topo();
int maxid=0;
for(int i=1;i<=n;i++){
cout<<AC::total[i]<<endl;
}
}
最小圆覆盖¶
给出 \(N\) 个点,让你画一个最小的包含所有点的圆。
输入:第一行一个整数 \(N\) 表示点的个数。接下来 \(N\) 行每行两个实数 \(x_i,y_i\) 表示点的坐标。最多两位小数。
输出:第一行一个实数表示圆的半径。第二行两个实数表示圆心的坐标。
#include<bits/stdc++.h>
#define N 114514
const double eps=0.000001;
using namespace std;
std::mt19937 rng(std::random_device{}());
bool is_equal(double A,double B){
if(fabs(A-B)<=eps) return 1;
return 0;
}
namespace geo{
struct point{
double x,y;
};
struct circle{
point O;
double r;
};
double dis(point A,point B){
double dis2=(A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
double res=sqrt(dis2);
return res;
}
int point_in_circle(point P,circle C){//in:1,on:0,out:-1
double dis_o=dis(P,C.O);
if(is_equal(dis_o,C.r)) return 0;
if(dis_o<C.r) return 1;
else return -1;
}
circle O_for_point(int N_p,point Plist[])//N_p:1,2,3
{
if(N_p==1)
return {Plist[0],0.0};
else if(N_p==2){
point reso={(Plist[0].x+Plist[1].x)/2.0,(Plist[0].y+Plist[1].y)/2.0};
double resr=dis(Plist[0],Plist[1])/2.0;
return {reso,resr};
}else{
point a=Plist[0],b=Plist[1],c=Plist[2];
double a1, a2, b1, b2, c1, c2;
point reso;
a1 = 2 * (b.x - a.x), b1 = 2 * (b.y - a.y),
c1 = b.x*b.x - a.x*a.x + b.y*b.y - a.y*a.y;
a2 = 2 * (c.x - a.x), b2 = 2 * (c.y - a.y),
c2 = c.x*c.x - a.x*a.x + c.y*c.y - a.y*a.y;
if (is_equal(a1, 0)) {
reso.y = c1 / b1;
reso.x = (c2 - reso.y * b2) / a2;
} else if (is_equal(b1, 0)) {
reso.x = c1 / a1;
reso.y = (c2 - reso.x * a2) / b2;
} else {
reso.x = (c2 * b1 - c1 * b2) / (a2 * b1 - a1 * b2);
reso.y = (c2 * a1 - c1 * a2) / (b2 * a1 - b1 * a2);
}
double resr=dis(reso,a);
return {reso,resr};
}
}
circle SEC(int N_p,point Plist[]){//下标从1开始
// point
shuffle(Plist+1,Plist+1+N_p,rng);
point p1[1]={Plist[1]};
circle res=O_for_point(1,p1);
for(int i=1;i<=N_p;i++)
{
if(point_in_circle(Plist[i],res)!=-1) continue;
point p2[2]={Plist[1],Plist[i]};
res=O_for_point(2,p2);
for(int j=2;j<=i;j++){
if(point_in_circle(Plist[j],res)!=-1) continue;
point p2[2]={Plist[i],Plist[j]};
res=O_for_point(2,p2);
for(int k=1;k<=j;k++)
{
if(point_in_circle(Plist[k],res)!=-1) continue;
point p3[3]={Plist[i],Plist[j],Plist[k]};
res=O_for_point(3,p3);
}
}
}return res;
}
}
geo::point P[N];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lf%lf",&P[i].x,&P[i].y);
//shuffle(P+1,P+1+n,rng);
geo::circle ans=geo::SEC(n,P);
printf("%.14lf\n%.14lf %.14lf\n",ans.r,ans.O.x,ans.O.y);
}
Lucas 定理¶
对于素数 \(p\),有
其中,当 \(n<k\) 时,二项式系数 \(\binom{n}{k}\) 规定为 0。
int C(int n, int m,int p) {
if(m==0) return 1;
return (C(n/p,m/p)*C(n%p,m%p))%p;
}
特别的对于较小的素数,考虑小规模数提前处理可以解决:
如\(p=3\)
int smallC[3][3] = {
{1, 0, 0},
{1, 1, 0},
{1, 2, 1}
};
int C(int n, int m) {
if(n<3&&m<3) return smallC[n][m];
if(m==0) return 1;
return (C(n/3,m/3)*C(n%3,m%3))%3;
}
bitset转int¶
int a=(int)b.to_ulong();
long long x = (long long)b.to_ullong();
//下为实际,上为常用
unsigned long x = b.to_ulong(); // ≤32 位
uint64_t y = b.to_ullong(); // ≤64 位
极大值极小值¶
const int INF = 0x3f3f3f3f;const long long LLINF = 0x3f3f3f3f3f3f3f3fLL;
快读快写¶
同样可以用于int128的输入输出等
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
x=x*10+ch-'0',ch=getchar();
return x*f;
}
//快读
void write(int x)
{
if(x<0)
putchar('-'),x=-x;
if(x>9)
write(x/10);
putchar(x%10+'0');
return;
}
//快写
输入输出优化¶
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
LCA¶
#include<bits/stdc++.h>
#define N 514514
using namespace std;
int n,m,s;
namespace BIT{
struct bitnode{//信息包括最大权值和权值和
};
}
namespace HLD{
//树的常规信息---
vector<int>tree[N];
int fa[N],size[N],depth[N];//深度用于lca
//---树的常规信息
int top[N];//链头
int wson[N];//重儿子
int hld_dfn[N],dfncnt;//dfn序(一条重链和一个子树dfn序连续
void dfs1(int u,int father){//第一次dfs找到重儿子,父亲,子树重量和深度
size[u]=1;fa[u]=father;
depth[u]=depth[father]+1;
for(int v:tree[u])
{
if(v!=father){//不回父边
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[wson[u]]) wson[u]=v;//重儿子更换
}
}
}
void dfs2(int u,int father,int utop){//第二次dfs创建重链,标记链头,找到dfn序
top[u]=utop;
hld_dfn[u]=(++dfncnt);//dfs序
//.........//可添加dfs序维护的东西
if(wson[u]!=0) dfs2(wson[u],u,utop);//先遍历重儿子,链头不变
for(int v:tree[u]){
if(v!=father&&v!=wson[u]){
dfs2(v,u,v);//再遍历轻儿子,链头变为轻儿子自身
}
}
}
int lca(int u,int v){//使用HLD找到LCA//也可改为返回ans
while(top[u]!=top[v])
{
if(depth[top[u]]>depth[top[v]]) swap(u,v);//保证v的链头深度较深
//...//这里可以更新dfs序中的[hld_dfn[top[v]],hld_dfn[v]]
v=fa[top[v]];
}
if(depth[u]>depth[v]) swap(u,v);// 保证v比较深
//...//这里可以更新[hld_dfn[u],hld_dfn[v]]
return u;
}
}
void precalc()
{
HLD::dfs1(s,0);
HLD::dfs2(s,0,s);
}
int main()
{
cin>>n>>m>>s;
for(int i=1;i<n;i++) {
int x,y;
cin>>x>>y;
HLD::tree[x].push_back(y);
HLD::tree[y].push_back(x);
}
precalc();
while(m--){
int a,b;
cin>>a>>b;
cout<<HLD::lca(a,b)<<endl;
}
}
FHQ treap¶
#include<bits/stdc++.h>
#define int long long
#define N 1145141
using namespace std;
struct node{
int val,size,pri;
int ls,rs;
}tree[N<<1];
int cnt,root;
int newnode(int date)
{
++cnt;
tree[cnt]={date,1,rand(),0,0};
return cnt;
}
void push_up(int now)
{
tree[now].size=tree[tree[now].ls].size+tree[tree[now].rs].size+1;
}
void spilt(int now,int x,int &L,int &R)
{
if(now==0){
L=R=0;return;
}
if(tree[now].val<=x){
L=now;
spilt(tree[now].rs,x,tree[now].rs,R);
}
else{
R=now;
spilt(tree[now].ls,x,L,tree[now].ls);
}
push_up(now);
}
int merge(int L,int R)
{
if(L==0||R==0) return L+R;
if(tree[L].pri>tree[R].pri)
{
tree[L].rs=merge(tree[L].rs,R);
push_up(L);
return L;
}else{
tree[R].ls=merge(L,tree[R].ls);
push_up(R);
return R;
}
}
int serchk(int now,int k)//求第k大数
{
if(tree[tree[now].ls].size+1==k) return tree[now].val;
if(tree[tree[now].ls].size>=k) return serchk(tree[now].ls,k);
else return serchk(tree[now].rs,k-tree[tree[now].ls].size-1);
}
void insert(int date)
{
int id=newnode(date);
int L,R;
spilt(root,date,L,R);
root=merge(merge(L,id),R);
}
void del(int date)
{
int L,p,R;
spilt(root,date,L,R);
spilt(L,date-1,L,p);
p=merge(tree[p].ls,tree[p].rs);
root=merge(merge(L,p),R);
}
int rankk(int date)
{
int L,R;
spilt(root,date-1,L,R);
int re=tree[L].size+1;
root=merge(L,R);
return re;
}
int pre(int date)//找到前趋
{
int L,R;
spilt(root,date-1,L,R);
int re=serchk(L,tree[L].size);
root=merge(L,R);
return re;
}
int nxt(int date)
{
int L,R;
spilt(root,date,L,R);
int re=serchk(R,1);
root=merge(L,R);
return re;
}
signed main()
{
srand(time(0));
int n;
scanf("%lld",&n);
while(n--)
{
int opt,x;
scanf("%lld%lld",&opt,&x);
switch(opt)
{
case 1:insert(x);break;
case 2:del(x);break;
case 3:printf("%lld\n",rankk(x));break;
case 4:printf("%lld\n",serchk(root,x));break;
case 5:printf("%lld\n",pre(x));break;
case 6:printf("%lld\n",nxt(x));break;
}
}
}
逆元¶
扩展欧几里得算法求逆元¶
求解逆元,就相当于求解线性同余方程。因此,可以使用 扩展欧几里得算法 在\(O(\log\min\{a,m\})\)时间内求解逆元。同时,由于逆元对应的线性方程比较特殊,可以适当地简化相应的步骤。
// Extended Euclidean algorithm.
void ex_gcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1;
y = 0;
} else {
ex_gcd(b, a % b, y, x);
y -= a / b * x;
}
}
// Returns the modular inverse of a modulo m.
// Assumes that gcd(a, m) = 1, so the inverse exists.
int inverse(int a, int m) {
int x, y;
ex_gcd(a, m, x, y);
return (x % m + m) % m;
}
这一算法适用于所有逆元存在的情形。
快速幂求逆元¶
这一方法主要适用于模数是素数 \(p\) 的情形.此时,由 费马小定理 可知对于任意 \(a\perp p\) 都有
根据逆元的唯一性可知,逆元 \(a^{-1}\bmod p\) 就等于 \(a^{p-2}\bmod p\),因此可以直接使用 快速幂 在 \(O(\log p)\) 时间内计算:
// Binary exponentiation.
int pow(int a, int b, int m) {
long long res = 1, po = a;
for (; b; b >>= 1) {
if (b & 1) res = res * po % m;
po = po * po % m;
}
return res;
}
// Returns the modular inverse of a prime modulo p.
int inverse(int a, int p) { return pow(a, p - 2, p); }
范德蒙德卷积¶
组合意义:从 \(n\) 个一类物品、\(m\) 个二类物品里一共选出 \(k\) 个,按"选了几个一类物品"分类,就是 \(\sum_{i}\binom{n}{i}\binom{m}{k-i}\);而不管怎么分类,总的选法就是 \(\binom{n+m}{k}\),两边相等。
语法,STL 记录¶
reverse(a, a + n)可以翻转 \(a\) 的前 \(n\) 个元素(需要<algorithm>)。scanf()返回EOF(即 \(-1\))时表示输入结束 / 读入失败。low = x & -x取出 \(x\) 最低位的 1(lowbit);__builtin_ctz(low)返回这个 1 所在的位置(从 0 开始计),常用于状压 dp。map内部按 key 升序排序(红黑树,弱序),遍历写法:
for (auto& [key, val] : mp) {
cout << key << " " << val << "\n";
}