板子
typedef __int128_t lll; lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。 upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。 getline(cin,name) next_permutation(a,a+n)//a[n]={1,2,..,n},用一次,输出一次(会变成下个排列)O(n)
读入&输出优化
inline int read(){ int ret=0,k=1;char c=getchar(); while(!isdigit(c)){ if(c=='-') k=-1; c=getchar(); } while(isdigit(c)){ ret=(ret<<1)+(ret<<3)+c-'0'; c=getchar(); } return k*ret; } inline double dbread(){ double X=0,Y=1.0;bool w=false;char ch=0; while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); } while(isdigit(ch)) { X=X*10+(ch^48); ch=getchar(); } ch=getchar(); while(isdigit(ch)){ X+=(Y/=10)*(ch^48); ch=getchar(); } return w?-X:X; } #define QWtype int char qw[40]; void QW(QWtype x){ if(x==0){putchar('0');return;} if(x<0) putchar('-'); int qwcnt=0; x=((x>0)?x:-x); while(x>0){ qw[++qwcnt]=x%10+'0'; x/=10; } while(qwcnt>0)putchar(qw[qwcnt--]); }
二分
int l,r,mid; while(l<r){ mid=l+r>>1; if(chk(mid)) r=mid; else l=mid+1ll; } //l
三分
int lef,rig,m1,m2; while(lef<rig){ m2=(rig-lef+1)/3;m1=lef+m2;m2+=m1; if(chk(m1,m2)) rig=m2-1; else lef=m1+1; } //lef
离散化
sort(p+1,p+1+m); m=unique(p+1,p+m+1)-p-1; for(int i=1;i<=n;++i){ b[i]=lower_bound(p+1,p+1+m,b[i])-p; }
折半搜索
inline ll search(ll m){ if(m<b[1].x) return 0; int l=1,r=tot,mid; while(l<r){ mid=l+r+1>>1; if(b[mid].x<=m) l=mid; else r=mid-1; } return b[l].w; } inline void dfs1(int u,ll sw,ll sx){ if(u>nn){ b[++cnt]=(thing){sx,sw}; return; } dfs1(u+1,sw,sx); if(sx+a[u].x<=m) dfs1(u+1,sw+a[u].w,sx+a[u].x); } inline void dfs2(int u,ll sw,ll sx){ if(u>n){ sw+=search(m-sx); ans=max(ans,sw); return; } dfs2(u+1,sw,sx); if(sx+a[u].x<=m) dfs2(u+1,sw+a[u].w,sx+a[u].x); }
莫队
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=200005,K=1000005; struct query{ int l,r,n; }q[N]; int a[N],s[K],f[N],n,m,t; ll ans[N]; bool cmp(query a,query b){ if(f[a.l]!=f[b.l]) return f[a.l]<f[b.l]; return a.r<b.r; } ll add(int i){ ++s[a[i]]; return 1ll*a[i]*((s[a[i]]<<1)-1); } ll remove(int i){ --s[a[i]]; return 1ll*a[i]*(s[a[i]]<<1|1); } int main(){ scanf("%d%d",&n,&t); for(int i=1;i<=n;++i) scanf("%d",&a[i]); for(int i=1;i<=t;++i){ scanf("%d%d",&q[i].l,&q[i].r); q[i].n=i; } m=sqrt(n); for(int i=1,k=1;i<=n;++k) for(int j=1;i<=n&&j<=m;++i,++j) f[i]=k; sort(q+1,q+1+t,cmp); int l=1,r=1; ll tmp=1ll*a[1]; ++s[a[1]]; for(int i=1;i<=t;++i){ while(r<q[i].r){ ++r;tmp+=add(r); } while(r>q[i].r){ tmp-=remove(r);--r; } while(l>q[i].l){ --l;tmp+=add(l); } while(l<q[i].l){ tmp-=remove(l);++l; } ans[q[i].n]=tmp; } for(int i=1;i<=t;++i) printf("%lld\n",ans[i]); return 0; }
高精度
struct Number{ int a[N],n;bool POrN; void init(){ memset(a,0,sizeof(a)); n=1;POrN=true; } void read(){ init();--n; char c=getchar(); while(!isdigit(c)){ if(c=='-') POrN=false; c=getchar(); } while(isdigit(c)){ a[n++]=c-'0';c=getchar(); } } void write(){ if(!POrN) cout<<"-"; for(int i=0;i<n;++i) cout<<a[i]; } Number abs(const Number a){ Number c=a; c.POrN=true;return c; } friend bool operator < (const Number a,const Number b){ if(a.POrN^b.POrN) return !a.POrN; if(a.n^b.n) return a.n<b.n; for(int i=0;i<a.n;++i) if(a.a[i]^b.a[i]) return a.a[i]<b.a[i]; return false; } friend bool operator <= (const Number a,const Number b){ if(a.POrN^b.POrN) return !a.POrN; if(a.n^b.n) return a.n<b.n; for(int i=0;i<a.n;++i) if(a.a[i]^b.a[i]) return a.a[i]<=b.a[i]; return false; } friend bool operator > (const Number a,const Number b){ return b<a; } friend bool operator >= (const Number a,const Number b){ return b<=a; } void tran(){ for(int i=0;i<(n>>1);++i){ swap(a[i],a[n-i-1]); } } Number add(Number a,Number b){ Number c;c.init(); c.n=max(a.n,b.n); a.tran();b.tran(); for(int i=0;i<c.n;++i){ c.a[i]+=a.a[i]+b.a[i]; if(c.a[i]>=10){ c.a[i]-=10;++c.a[i+1]; } } while(c.a[c.n]) ++c.n; c.tran(); return c; } Number sub(Number a,Number b){ Number c;c.init(); c.n=max(a.n,b.n); a.tran();b.tran(); for(int i=0;i<c.n;++i){ c.a[i]+=a.a[i]-b.a[i]; if(c.a[i]<0) c.a[i]+=10,--c.a[i+1]; } while(c.n>1&&!c.a[c.n-1]) --c.n; c.tran(); return c; } friend Number operator + (Number a,Number b){ Number c;bool fl=false; if(a.POrN^b.POrN){ if(!a.POrN){ swap(a,b);fl=true; } if(a.abs(a)<b.abs(b)){ c=c.sub(b,a);c.POrN=false; } else{ c=c.sub(a,b);c.POrN=true; } } else{ c=c.add(a,b);c.POrN=a.POrN; } if(fl) swap(a,b); return c; } friend Number operator - (const Number a,const Number b){ Number c=b;c.POrN=!c.POrN;return a+c; } friend Number operator * (Number a,Number b){ Number c;c.init(); c.n=a.n+b.n; c.POrN=!(a.POrN^b.POrN); a.tran();b.tran(); for(int i=0;i<a.n;++i) for(int j=0;j<b.n;++j) c.a[i+j]+=a.a[i]*b.a[j]; for(int i=0;i<c.n;++i) if(c.a[i]>10){ c.a[i+1]+=c.a[i]/10;c.a[i]%=10; } while(c.n>1&&!c.a[c.n-1]) --c.n; a.tran();b.tran();c.tran(); return c; } bool cmp(Number c,int s,int t){ if(t-s>n) return true; if(t-s<n) return false; for(int i=0;i<n;++i){ if(a[i]==c.a[s+i]) continue; return c.a[s+i]>a[i]; } return true; } void sub(Number b,int s,int t){ for(int i=t-1,j=b.n-1;j>=0;--i,--j){ a[i]-=b.a[j]; if(a[i]<0) a[i]+=10,--a[i-1]; } return; } friend Number operator / (Number a,Number b){ Number c,d=a;c.init(); c.POrN=!(a.POrN^b.POrN);c.n=0; for(int s=0,t=b.n;t<=d.n;){ while(b.cmp(d,s,t)){ d.sub(b,s,t);++c.a[c.n]; if(!d.a[s]) ++s; } ++c.n;++t; if(!d.a[s]) ++s; } int cnt=0; while(cnt<c.n&&!c.a[cnt]) ++cnt; c.n-=cnt; for(int i=0;i<c.n;++i) c.a[i]=c.a[i+cnt]; if(!c.n){ ++c.n;c.POrN=true; } return c; } };
graph
边链表
struct graph{ int nxt,to; }e[M]; int g[N],n,m,cnt; inline void addedge(int x,int y){ e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y; } inline void dfs(int u){ for(int i=g[u],k;i;i=e[i].nxt)
最短路
spfa
inline void spfa(int u){ q.push(u);inq[u]=true; while(!q.empty()){ u=q.front();q.pop();inq[u]=false; for(int i=g[u];i;i=e[i].nxt) if(dis[u]+e[i].w>dis[e[i].to]){ dis[e[i].to]=dis[u]+e[i].w; if(!inq[e[i].to]){ inq[e[i].to]=true;q.push(e[i].to); } } } }
dijkstra
struct dist{ int u;ll dis; dist(int _u,int _dis){ u=_u;dis=_dis; } friend bool operator < (const dist b,const dist a){ return a.dis<b.dis; } }; ll dis[N]; priority_queue<dist> q; void dijkstra(int u){ for(int i=1;i<=n;++i) dis[i]=-1; dis[u]=0; q.push(dist(u,dis[u])); while(!q.empty()){ dist d=q.top();u=d.u;q.pop(); if(d.dis>dis[u]) continue; for(int i=g[u];i;i=e[i].nxt) if(dis[e[i].to]==-1||dis[u]+e[i].w<dis[e[i].to]){ dis[e[i].to]=dis[u]+e[i].w; q.push(dist(e[i].to,dis[e[i].to])); } } }
强连通分量
tarjan
时间复杂度:\(O(n+m)\)
用途:有向图缩环
int f[N],dfn[N],low[N],sta[N],top; /*dfn[u]:遍历到u点的时间; low[u]:u点可到达的各点中最小的dfn[v],即最高层的点*/ bool ins[N]; inline void tarjan(int u){ dfn[u]=low[u]=++cnt; sta[++top]=u;ins[u]=true; for(int i=g[u];i;i=e[i].nxt) if(!dfn[e[i].to]){ tarjan(e[i].to); low[u]=min(low[u],low[e[i].to]); } else if(ins[e[i].to]) low[u]=min(low[u],low[e[i].to]); if(dfn[u]==low[u]){ while(sta[top+1]!=u){ f[sta[top]]=u; ins[sta[top--]]=false; } } } inline void solve(){ for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); }
割边
割边,又称桥.
\(dfn[\;],low[\;]\)的定义同\(tarjan\)求有向图强连通分量.
枚举当前点\(u\)的所有邻接点\(v\):
1.如果某个邻接点\(v\)未被访问过,则访问\(v\),并在回溯后更新\(low[u]=min(low[u],low[v])\);
2.如果某个邻接点\(v\)已被访问过,则更新\(low[u]=min(low[u],dfn[v])\).
对于当前节点\(u\),如果邻接点中存在一点\(v\)满足\(low[v]>dfn[u]\)(\(v\)向上无法到达\(u\)及\(u\)祖先)说明\((u,v)\)为一条割边.
inline void tarjan(int u,int fa){ dfn[u]=low[u]=++cnt; for(int i=g[u];i;i=e[i].nxt) if(!dfn[e[i].to]){ tarjan(e[i].to,u); low[u]=min(low[u],low[e[i].to]); if(low[e[i].to]>dfn[u]) cut[e[i].n]=true; } else if(e[i].to!=fa) low[u]=min(low[u],dfn[e[i].to]); }
割点
\(dfn[\;],low[\;]\)的定义同\(tarjan\)求有向图强连通分量.
枚举当前点\(u\)的所有邻接点\(v\):
1.如果某个邻接点\(v\)未被访问过,则访问\(v\),并在回溯后更新\(low[u]=min(low[u],low[v])\);
2.如果某个邻接点\(v\)已被访问过,则更新\(low[u]=min(low[u],dfn[v])\).
对于当前节点\(u\),
如果\(u\)为搜索树中的根节点,若它的子节点数\(\geq2\)(根是多棵子树上节点的唯一连通方式),则\(u\)为割点;
如果\(u\)为搜索树上的非根节点,若存在子节点\(v\)满足\(low[v]\;\geq\;dfn[u]\)(\(v\)向上无法到达\(u\)的祖先),则\(u\)为割点.
inline void tarjan(int u,int fa){ dfn[u]=low[u]=++cnt; for(int i=g[u];i;i=e[i].nxt){ ++t[u]; if(!dfn[e[i].to]){ tarjan(e[i].to,u); low[u]=min(low[u],low[e[i].to]); if(u==rt){ if(t[u]>=2) cut[u]=true; } else if(low[e[i].to]>=dfn[u]) cut[u]=true; } else if(e[i].to!=fa) low[u]=min(low[u],dfn[e[i].to]); } }
匈牙利算法
时间复杂度:\(O(m\sqrt{n})\)
#define N 3001 #define M 200001 struct graph{ int nxt,to; }e[M]; int g[N],fr[N],n,m,cnt; bool u[N]; inline void addedge(int x,int y){ e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y; } inline bool match(int x){ for(int i=g[x];i;i=e[i].nxt) if(!u[e[i].to]){ u[e[i].to]=true; if(!fr[e[i].to]||match(fr[e[i].to])){ fr[e[i].to]=x;return true; } } return false; } inline int hungary(){ int ret=0; for(int i=1;i<=n;i++){ fill(u+1,u+1+n,false); if(match(i)) ret++; } return ret; } inline void init(){ scanf("%d%d",&n,&m); for(int i=1,j,k;i<=m;i++){ scanf("%d%d",&j,&k); addedge(j,k); } printf("%d",hungary()); }
2-SAT
- 根据条件表达式建边:\((x,y)\)表示如果选了\(x\)必须选\(y\);
- 缩环;
- 判断是否可行;
- 根据缩完环的图反向建边;
- 拓扑排序进行染色(\(1\)表示\(true,2\)表示\(false\))。
时间复杂度:\(O(n+m)\)
/*假设输入是无空格无括号的的c++条件表达式且未知数用编号代替*/ #define L 11 #define N 100001 #define M 1000001 struct graph{ int nxt,to; }e[M],gra[M]; int sol[N],col[N]; int f[N],dfn[N],low[N],sta[N]; int g[N],go[N],to[N],n,m,nn,cnt; bool ins[N];char c[L];queue<int> q; inline addedge(int x,int y){ e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y; } inline adde(int x,int y){ gra[++cnt].nxt=go[x];go[x]=cnt;gra[cnt].to=y; } inline void type(){ int l=strlen(c),i=0,x=0,y=0; bool flag1=true,flag2=true; if(c[i]=='!'){ flag1=false;i++; } while(i<l&&c[i]>='0'&&c[i]<='9') x=x*10+c[i++]-'0'; if(i==l){ if(flag1) addedge(x+n,x); else addedge(x,x+n); return; } char cha=c[i];i++; if(c[i]=='!'){ flag2=false;i++; } while(i<l&&c[i]>='0'&&c[i]<='9') y=y*10+c[i++]-'0'; if(cha=='&'){ if(flag1&&flag2){ addedge(x+n,x); addedge(y+n,y); } else if(flag1){ addedge(x+n,x); addedge(y,y+n); } else if(flag2){ addedge(x,x+n); addedge(y+n,y); } else{ addedge(x,x+n); addedge(y,y+n); } } else if(cha=='|'){ if(flag1&&flag2){ addedge(x+n,y); addedge(x,y+n); } else if(flag1){ addedge(x+n,y+n); addedge(y,x); } else if(flag2){ addedge(x,y); addedge(y+n,x+n); } else{ addedge(x,y+n); addedge(y,x+n); } } else if(cha=='^'){ if(flag1&&flag2){ addedge(x,y+n); addedge(x+n,y); addedge(y,x+n); addedge(y+n,x); } else if(flag1){ addedge(x,y); addedge(x+n,y+n); addedge(y,x); addedge(y+n,x+n); } else if(flag2){ addedge(x,y); addedge(x+n,y+n); addedge(y,x); addedge(y+n,x+n); } else{ addedge(x,y+n); addedge(x+n,y); addedge(y,x+n); addedge(y+n,x); } } } inline void tarjan(int u){ dfn[u]=low[u]=++cnt; sta[++sta[0]]=u;ins[u]=true; for(int i=g[u];i;i=e[i].nxt) if(!dfn[e[i].to]){ tarjan(e[i].to); low[u]=min(low[u],low[e[i].to]); } else if(ins[e[i].to]) low[u]=min(low[u],low[e[i].to]); if(dfn[u]==low[u]){ while(sta[sta[0]+1]!=u){ f[sta[sta[0]]]=u; ins[sta[0]--]=false; } } } inline bool chk(){ for(int i=1;i<=n;i++) if(f[i]==f[i+n]) return false; return true; } inline void build(){ cnt=0; for(int i=1;i<=nn;i++) for(int j=g[i];j;j=e[j].nxt){ if(f[i]!=f[e[j].to]){ adde(f[e[j].to],f[i]);to[i]++; } } } inline void toposort(){ for(int i=1;i<=nn;i++) if(f[i]==i&&!to[i]) q.push(i); while(!q.empty()){ int u=q.front();q.pop(); if(!col[u]){ col[u]=1;col[u+n]=-1; } for(int i=go[u];i;i=gra[i].nxt) if(!(--to[gra[i].to])) q.push(gra[i].to); } } inline void init(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%s",&c);type(); } nn=n<<1;cnt=0; for(int i=1;i<=nn;i++) if(!dfn[i]) tarjan(i); if(!chk()){ printf("No solution.\n"); return; } build(); toposort(); for(int i=1;i<=n;i++) col[i]=col[f[i]]; for(int i=1;i<=n;i++) if(col[i]>0) printf("%d:true\n",i); else printf("%d:false\n",i); }
网络流
最大流
- 二分图最大匹配
- KM(最大权匹配)
- 最小路径覆盖:在一个有向图中,找出最少的路径,使得这些路径经过了所有的点。z.B. 1->3>4,2,5
- 每个顶点i属于且只属于一个路径。
- 拆点
- 路径数=顶点数-匹配数
- 二分图多重匹配
inline void adde(int x,int y,int f){ addedge(x,y,f);addedge(y,x,0); } inline bool bfs(int u){ memset(dep,0,sizeof(dep)); q.push(u);dep[u]=1; while(!q.empty()){ u=q.front();q.pop(); for(int i=g[u];i;i=e[i].nxt) if(e[i].f>0&&!dep[e[i].to]){ q.push(e[i].to); dep[e[i].to]=dep[u]+1; } } return dep[t]; } inline int dfs(int u,int f){ if(u==t) return f; int ret=0; for(int i=g[u],d;i&&f;i=e[i].nxt) if(e[i].f>0&&dep[e[i].to]>dep[u]){ d=dfs(e[i].to,min(e[i].f,f)); f-=d;ret+=d;e[i].f-=d;e[i^1].f+=d; } if(!ret) dep[u]=-1; return ret; } inline int dinic(){ int ret=0; while(bfs(s)) ret+=dfs(s,inf); return ret; }
最小割=最大流
- 割:一种 点的划分方式:将所有的点划分为\(S\)和\(T=V-S\) 两个集合,其中源点属于\(S\),汇点属于\(T\)。
- 最大权闭合子图=总收益-最大流
- 最大点权独立集=总点权-最大流
费用流
- 最长不相交路径
- 最大权不相交路径
inline void adde(int x,int y,int f,int w){ addedge(x,y,f,w);addedge(y,x,0,-w); } inline bool spfa(int u){ for(int i=1;i<=t;++i){ dis[i]=INF;inq[i]=false; } q.push(u);dis[u]=0;inq[u]=true; while(!q.empty()){ u=q.front();q.pop();inq[u]=false; for(int i=g[u];i;i=e[i].nxt) if(e[i].f>0&&dis[u]+e[i].w<dis[e[i].to]){ dis[e[i].to]=dis[u]+e[i].w; pre[e[i].to].e=i;pre[e[i].to].v=u; if(!inq[e[i].to]){ q.push(e[i].to);inq[e[i].to]=true; } } } return dis[t]<INF; } inline int mf(){//最小费用最大流 int ret=0,d; while(spfa(s)){ d=INF; for(int i=t;i!=s;i=pre[i].v) d=min(d,e[pre[i].e].f); ret+=d*dis[t]; for(int i=t;i!=s;i=pre[i].v){ e[pre[i].e].f-=d; e[pre[i].e^1].f+=d; } } return ret; }
inline void adde(int x,int y,int f,int w){ addedge(x,y,f,w);addedge(y,x,0,-w); } inline bool spfa(int u){ for(int i=0;i<(t<<1);++i){ dis[i]=-INF;inq[i]=false; } q.push(u);dis[u]=0;inq[u]=true; while(!q.empty()){ u=q.front();q.pop();inq[u]=false; for(int i=g[u];i;i=e[i].nxt){ if(e[i].f>0&&dis[u]+e[i].w>dis[e[i].to]){ dis[e[i].to]=dis[u]+e[i].w; pre[e[i].to].e=i;pre[e[i].to].v=u; if(!inq[e[i].to]){ inq[e[i].to]=true;q.push(e[i].to); } } } } return dis[t]>-INF; } inline int mf(){//最大费用最大流 int ret=0,d; while(true){ if(!spfa(s)) return ret; d=INF; for(int i=t;i!=s;i=pre[i].v){ d=min(d,e[pre[i].e].f); } ret+=d*dis[t]; for(int i=t;i!=s;i=pre[i].v){ e[pre[i].e].f-=d;e[pre[i].e^1].f+=d; } } }
有上下界的网络流
基本建图
建立超级源\(S\),超级汇\(T\).
对于边\((u,v)\)=\([l,u]\),将其拆成三条边:
- \((S,v)=l\);
- \((u,v)=u-l\);
- \((u,T)=l.\)
因为对于边\((u,v)=[l,u]\),
\(u\)至少流出\(l\)的流量,\(v\)至少流入\(l\)的流量,所以建边\((S,v)=l,(u,T)=l\);
而\(u->v\)有\(u-l\)的流量是自由流,所以建边\((u,v)=u-l\).
显然\(S->u,u->T\)有可能有多条边,合并这些边,节省空间.
- 无源汇
可行流
求\(S->T\)的最大流,从\(S\)出发的边全部满流则可行,因为说明所有边的下界均已满足.每条边的实际流为自由流+流量下界. - 有源汇
可行流
加一条边\((t,s)=+\infty\).转成无源汇.
求\(S->T\)的最大流,从\(S\)出发的边全部满流则可行.
最大流
求出可行流后,在残量网络上求\(s->t\)的最大流.
理由:
\(s->t\)跑的是\(S->T\)的反向边,这时下界的流量已经在反向边中了,\((t,s)=+\infty,S,T\)不会影响到最大流,所以是合法的答案.
最小流
先不加\((t,s)=+\infty\)这条边,这时跑\(S->T\)的最大流可求出\(t->s\)的最大流,也就是在合法的情况下最多能减去多少.
然后再加\((t,s)=+\infty\)这条边,此时残量网络\(S->T\)的最大流即为答案.
tree
lca
\(f[i][j]\)表示节点\(i\)的祖先中,与节点\(i\)距离为\(2^j\)的节点编号.
那么\(f[i][j]=\begin{cases}root&i=root\\ father[i]&j=0,i\not=root\\ f[i][j]=f[f[i][j-1]][j-1]&j>0,i\not=root\end{cases}\)
#define K 20 #define N 10005 #define M 100005 struct graph{ int nxt,to; }e[M]; int f[N][K],g[N],dep[N],m,n,q,cnt; stack<int> s; inline void addedge(int x,int y){ e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y; } inline void dfs(int u){ dep[u]=1;s.push(u); while(!s.empty()){ u=s.top();s.pop(); if(u!=1) for(int i=1;i<K;++i) f[u][i]=f[f[u][i-1]][i-1]; else for(int i=0;i<K;++i) f[u][i]=u; for(int i=g[u];i;i=e[i].nxt){ if(!dep[e[i].to]){ dep[e[i].to]=dep[u]+1; f[e[i].to][0]=u; s.push(e[i].to); } } } } inline int swim(int x,int h){ for(int i=0;h;++i,h>>=1) if(h&1) x=f[x][i]; return x; } inline int lca(int x,int y){ if(dep[x]<dep[y]) swap(x,y); x=swim(x,dep[x]-dep[y]); if(x==y) return x; int i=K-1; while(true){ if(f[x][0]==f[y][0]) return f[x][0]; for(;f[x][i]==f[y][i];--i); x=f[x][i];y=f[y][i]; } } inline void init(){ scanf("%d%d",&n,&m); for(int i=1,j,k;i<=m;++i){ scanf("%d%d",&j,&k); addedge(j,k);addedge(k,j); } scanf("%d",&q);dfs(1); for(int i=1,j,k;i<=q;i++){ scanf("%d%d",&j,&k); printf("%d\n",lca(j,k)); } }
树链剖分
基本思想
一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每条边属于且只属于一条链,然后再通过数据结构来维护每一条链。
一些定义
树链:树上的路径.
剖分:把路径分类为重链和轻链.
重儿子:\(u\)的子节点中\(siz[v]\)值最大的\(v\).
轻儿子:\(u\)的其它子节点.
重边:点\(u\)与其重儿子的连边.
轻边:点\(u\)与其轻儿子的连边.
重链:由重边连成的路径.
轻链:轻边.
性质
- 如果\((u,v)\)为轻边,则\(siz[v]\;\times\;2<siz[u]\).
- 从根到某一点的路径上轻链、重链的个数都不大于\(logn\).
- 树剖序其实也可以是\(dfs\)序的一种.
实现
一些变量:
\(f[u]\)表示\(u\)的父亲.
\(siz[u]\)表示以\(u\)为根的子树的大小.
\(dep[u]\)表示\(u\)的深度(根深度为\(1\)).
\(top[u]\)表示\(u\)所在的链的顶端节点.
\(son[u]\)表示与\(u\)的重儿子.
重标号:
\(p[u]\):重标号后\(u\)的编号.
\(dfs\)序:\(dfs\)的时候先走重边.
这样可以使得重边的编号是连续的,方便维护.
用两遍\(dfs\)求出所需的所有变量以及重标号.
预处理
int f[N],p[N],dep[N],siz[N],son[N],top[N]; /*top[u]:u所在的链的顶端节点,son[u]:u的重儿子*/ inline void dfs1(int u){ int m=0;siz[u]=1; for(int i=g[u];i;i=e[i].nxt) if(!dep[e[i].to]){ f[e[i].to]=u; dep[e[i].to]=dep[u]+1; dfs1(e[i].to); siz[u]+=siz[e[i].to]; if(siz[e[i].to]>m){ son[u]=e[i].to; m=siz[e[i].to]; } } } inline void dfs2(int u,int tp){ top[u]=tp;p[u]=++cnt;ww[cnt]=w[u]; if(son[u]) dfs2(son[u],tp); for(int i=g[u];i;i=e[i].nxt){ if(e[i].to!=f[u]&&e[i].to!=son[u]) dfs2(e[i].to,e[i].to); } }
访问修改(u,v):
类似倍增的走法,每次将深度大的往上移,直到\(u,v\)属于同一条链.
inline int sum(int x,int y){ int ret=0,t; while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]){ t=x;x=y;y=t; } ret+=ask(1,p[top[x]],p[x]); x=f[top[x]]; } if(p[x]>p[y]){ t=x;x=y;y=t; } ret+=ask(1,p[x],p[y]); return ret; } inline void change(int x,int y,int k){ int t; while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]){ t=x;x=y;y=t; } cover(1,p[top[x]],p[x],k); x=f[top[x]]; } if(p[x]>p[y]){ t=x;x=y;y=t; } cover(1,p[x],p[y],k); }
数据结构
并查集
inline int gf(int k){ if(f[k]==k) return k; int fa=gf(f[k]); return f[k]=fa; }
树状数组
inline int lowbit(int x){ return x&(-x); } inline void add(int x,int k){ for(int i=x;i<=n;i+=lowbit(i)) s[i]+=k; } inline int ask(int x){ int ret=0; for(int i=x;i;i-=lowbit(i)) ret+=s[i]; return ret; }
线段树
struct SegMent{ int l,r;ll sum/*ans*/,lzy; }lt[M]; inline void build(int u,int l,int r){ lt[u].l=l;lt[u].r=r; if(lt[u].l<lt[u].r){ int lef=u<<1,rig=u<<1|1; int mid=(lt[u].l+lt[u].r)>>1; build(lef,l,mid);build(rig,mid+1,r); } else{ int p=w[lt[u].l]; lt[u].sum=ans[p]; } } inline void pushdown(int u){ if(!lt[u].lzy&&!lt[u].lzt) return; if(lt[u].l<lt[u].r){ int lef=u<<1,rig=u<<1|1;ll s; s=lt[u].lzy;lt[lef].lzy+=s;lt[rig].lzy+=s; s=lt[u].lzt;lt[lef].lzt+=s;lt[rig].lzt+=s; } else{ ll s=lt[u].lzy;int p=w[lt[u].l]; lt[u].sum+=1ll*(siz[p]-siz[son[p]])*s; s=lt[u].lzt;lt[u].sum+=1ll*(tot[p]<<1ll)*s; } lt[u].lzy=lt[u].lzt=0; } inline void add(int u,int x,ll s){ pushdown(u); if(lt[u].l==lt[u].r){ lt[u].sum+=s;return; } if(lt[u].l<lt[u].r){ int lef=u<<1,rig=u<<1|1; int mid=(lt[u].l+lt[u].r)>>1; if(x<=mid) add(lef,x,s); else add(rig,x,s); } } inline ll ask(int u,int x){ pushdown(u); if(lt[u].l==lt[u].r) return lt[u].sum; if(lt[u].l<lt[u].r){ int lef=u<<1,rig=u<<1|1; int mid=(lt[u].l+lt[u].r)>>1; if(x<=mid) return ask(lef,x); return ask(rig,x); } }
堆
inline void swim(int i){ heap a=h[i];int j=i>>1; while(j&&chk(a,h[j])){ h[i]=h[j];num[h[i].n]=i;i=j;j>>=1; } h[i]=a;num[h[i].n]=i; } inline void sink(int i){ heap a=h[i];int j=i<<1; while(j<=cnt){ if(j<cnt&&chk(h[j+1],h[j])) ++j; if(chk(a,h[j])) break; h[i]=h[j];num[h[i].n]=i;i=j;j<<=1; } h[i]=a;num[h[i].n]=i; }
可并堆
可以支持合并的堆.
/*大根堆*/ struct heap{ int l,r,w,d; }h[N]; int rt[N];//第i个堆的根的下标 /*合并以x,y为根的堆*/ inline int merge(int x,int y){ //其中一个堆为空 if(!x||!y) return x+y; //使得x,y两个根中x大 if(h[x].w<h[y].w) swap(x,y); //保持堆两边的平衡 h[x].r=merge(y,h[x].r); if(h[h[x].l].d<h[h[x].r].d) swap(h[x].l,h[x].r); h[x].d=h[h[x].r].d+1; return x; } inline int pop(int x){ int l=h[x].l,r=h[x].r; h[x].l=h[x].r=h[x].w=0; return merge(l,r); } inline int top(x){ return h[x].w; }
splay
时间复杂度:\(O(nlogn)\).
#define N 100005 struct Splay{ int c[2],f,siz,val,cnt;//balabala...(根据题目需要的变量) }tr[N]; int n,rt,cnt; inline bool son(int u){ return tr[tr[u].f].c[1]==u; } inline void ins_p(int f,int u,int c){ tr[f].c[c]=u;tr[u].f=f; } inline void recnt(int u){ tr[u].siz=tr[tr[u].c[0]].siz+tr[tr[u].c[1]].siz+tr[u].cnt; } inline void rotate(int u){ int f=tr[u].f;bool c=son(u); if(tr[f].f) ins_p(tr[f].f,u,son(f)); else tr[u].f=0,rt=u; ins_p(f,tr[u].c[c^1],c); ins_p(u,f,c^1); recnt(f);recnt(u); } inline void splay(int u,int rt){ while(tr[u].f!=rt){ if(tr[tr[u].f].f==rt) rotate(u); else if(son(tr[u].f)==son(u)){ rotate(tr[u].f);rotate(u); } else{ rotate(u);rotate(u); } } } inline int kth(int k)/*第k小的值*/{ int u=rt; while(u){ if(k<=tr[tr[u].c[0]].siz) u=tr[u].c[0]; else{ k-=tr[tr[u].c[0]].siz; if(k<=tr[u].cnt) return tr[u].val; k-=tr[u].cnt;u=tr[u].c[1]; } } return u; } inline int near(int u,bool c){ if(tr[u].c[c]){ u=tr[u].c[c];c^=1; while(tr[u].c[c]) u=tr[u].c[c]; return u; } while(u&&son(u)==c) u=tr[u].f; return tr[u].f; } inline int select(int u,int v){ u=near(u,0);v=near(v,1); splay(u,0);splay(v,rt); return tr[v].c[0]; } inline void clear(int u){ tr[tr[u].f].c[son(u)]=0; tr[u].c[0]=tr[u].c[1]=tr[u].f=0; tr[u].siz=tr[u].val=tr[u].cnt=0; } inline void del(int u,int v){ u=select(u,v); int f=tr[u].f;clear(u);u=f; while(u){ recnt(u);u=tr[u].f; } } inline int find(int k){ int u=rt; while(u&&tr[u].val!=k) u=tr[u].c[k>tr[u].val]; return u; } inline void insert(int k){ int u=find(k); if(u){ ++tr[u].cnt; while(u){ recnt(u);u=tr[u].f; } return; } u=rt; while(tr[u].c[k>tr[u].val]) u=tr[u].c[k>tr[u].val]; tr[++cnt].val=k; tr[cnt].siz=tr[cnt].cnt=1; if(!u){ rt=cnt;recnt(rt);return; } ins_p(u,cnt,k>tr[u].val); splay(cnt,0); }
LCT
时间复杂度:\(O(nlogn)\).
#define N 100005 struct LCT{ int c[2],f,rev; }tr[N]; int n; stack<int> s; inline void down(int u){ if(tr[u].rev){ tr[u].rev=0; tr[tr[u].c[0]].rev^=1; tr[tr[u].c[1]].rev^=1; swap(tr[u].c[0],tr[u].c[1]); } } inline bool son(int u){ return tr[tr[u].f].c[1]==u; } inline void ins_p(int f,int u,int c){ tr[f].c[c]=u;tr[u].f=f; } inline bool isroot(int u){ return tr[tr[u].f].c[0]!=u&&tr[tr[u].f].c[1]!=u; } inline void recnt(int u){ //balabala... } inline void rotate(int u){ int f=tr[u].f;bool c=son(u); if(isroot(f)) tr[u].f=tr[f].f; else ins_p(tr[f].f,u,son(f)); ins_p(f,tr[u].c[c^1],c); ins_p(u,f,c^1); recnt(f);recnt(u); } inline void splay(int u){ s.push(u); for(int v=u;!isroot(v);v=tr[v].f) s.push(tr[v].f); while(!s.empty()){ down(s.top());s.pop(); } while(!isroot(u)){ if(isroot(tr[u].f)) rotate(u); else if(son(tr[u].f)==son(u)){ rotate(tr[u].f);rotate(u); } else{ rotate(u);rotate(u); } } } inline void access(int u){ for(int lst=0;u;lst=u,u=tr[u].f){ splay(u);tr[u].c[1]=lst;recnt(u); } } inline void makeroot(int u){ access(u);splay(u);tr[u].rev^=1; } inline int findroot(int u){ access(u);splay(u); while(down(u),tr[u].c[0]) u=tr[u].c[0]; splay(u); return u; } inline void select(int u,int v){ makeroot(u);access(v);splay(v);//u为v的左孩子 } inline void link(int u,int v){ makeroot(u);tr[u].f=v; } inline void cut(int u,int v){ select(u,v);tr[v].c[0]=tr[u].f=0;recnt(v); }
Dsu on tree
主要思想:暴力,最大子树只跑一遍。
时间复杂度:\(O(nlogn)\).
//求每个子树的众数和 ll sum[maxn],buk[maxn],mx; inline void up(int color){ if(buk[color]) sum[buk[color]] -= color; buk[color] ++ ; if(buk[color]) sum[buk[color]] += color; if(buk[color]>mx) mx=buk[color]; } inline void down(int color){ if(buk[color]==0) return; sum[buk[color]] -= color; if( buk[color]==mx && sum[mx]==0 ) mx--; buk[color] -- ; sum[buk[color]] += color; } int size[maxn],son[maxn],dfs[maxn],l[maxn],r[maxn],topdfs; void findheavy(int x,int fa){ dfs[++topdfs] = x; l[x]=topdfs; int mxsize=0; size[x]=1; for(int i=head[x];i;i=nxt[i]){ if(to[i]==fa) continue; findheavy(to[i],x); size[x] += size[to[i]]; if(size[to[i]]>mxsize){ son[x]=to[i]; mxsize=size[to[i]]; } } r[x]=topdfs; } ll ans[maxn]; void dsu(int x,int fa,bool keep){ for(int i=head[x];i;i=nxt[i]){ if(to[i]!=fa&&to[i]!=son[x]) dsu(to[i],x,0); } if(son[x]) dsu(son[x],x,1); for(int i=head[x];i;i=nxt[i]){ if(to[i]!=fa&&to[i]!=son[x]) for(int j=l[to[i]];j<=r[to[i]];j++) up(c[dfs[j]]); } up(c[x]); ans[x]=sum[mx]; if(keep==0){ for(int j=l[x];j<=r[x];j++) down(c[dfs[j]]); } }
ST表
给定一个数列\(a,O(nlogn)\)预处理,\(O(1)\)查询数列在区间\([l,r]\)的最值.
本文介绍求最大值.
预处理
\(st[i][j]\)表示\(max\{a_k\}(k\in[i,i+2^j))\).
\(st[i][j]=\begin{cases}a_i&j=0\\max(st[i][j-1],st[i+2^{j-1}][j-1])&j>0\\\end{cases}\)
询问
询问\([l,r]\),令\(k=\lfloor\;log_2^{r-l+1}\;\rfloor\),则答案为\(max(st[l][k],st[r-2^k+1][k])\).
int st[N][K],a[N],log_2[N]; inline void ini_st(){ log_2[1]=0; for(int i=2;i<=n;++i){ log_2[i]=log_2[i-1]; if((1<<log_2[i]+1)==i) ++log_2[i]; } for(int i=n;i;--i){ st[i][0]=a[i]; for(int j=1;(i+(1<<j)-1)<=n;++j) st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]); } } inline int ask(int l,int r){ int k=log_2[r-l+1]; return max(st[l][k],st[r-(1<<k)+1][k]); }
计算几何
struct point{ int x,y; }; point operator - (point a,point b){ return (point){a.x-b.x,a.y-b.y}; } ll operator * (point a,point b){ return 1ll*a.x*b.y-1ll*b.x*a.y; }
凸包
时间复杂度:\(O(nlogn)\).
#define N 100005 #define eps 1e-13 bool operator < (point a,point b){ if(a.x!=b.x) return a.x<b.x; return a.y<b.y; } inline double sqr(int k){ return (double)(k*k); } inline double dis(point x,point y){ return sqr(x.x-y.x)+sqr(x.y-y.y); } inline bool cmp(point x,point y){ if(fabs(x.an-y.an)<eps) return dis(x,a[1])>dis(y,a[1]); return x.an<y.an; } inline void convex(point a[],int &n){ for(int i=2;i<=n;++i) if(a[i]<a[1]) swap(a[i],a[1]); for(int i=2;i<=n;++i) a[i].an=atan2(a[i].y-a[1].y,a[i].x-a[1].x); sort(a+2,a+1+n,cmp); int m=1;a[++n]=a[1]; for(int i=2;i<=n;++i){ if(fabs(a[i].an-a[i-1].an)<eps) continue; while(m>1&&(a[i]-a[m-1])*(a[m]-a[m-1])>0) --m; a[++m]=a[i]; } n=m; }
旋转卡壳
时间复杂度:\(O(n)\).
#define N 100005 point a[N];int n; inline double sqr(int k){ return (double)(k*k); } inline double dis(point x){ return sqrt(sqr(x.x)+sqr(x.y)); } inline int Nxt(int k){ return (++k>n)?1:k; } inline double rotate(){ point p;double di,dia=0.0; if(n==1) return dia; for(int i=1,j=2;i<=n;++i){ p=a[Nxt(i)]-a[i]; while(abs(p*(a[j]-a[i]))<abs(p*(a[Nxt(j)]-a[i]))) j=Nxt(j); dia=max(dia,max(dis(a[i]-a[j]),dis(a[Nxt(i)]-a[Nxt(j)]))); } return dia; }
转角法
时间复杂度:\(O(n)\).
point a[N];int n; inline int cmp(ll x){ return x?(x>0?1:-1):0; } inline bool onseg(point p,point a,point b){ if(cmp((a-p)*(b-p))) return false; return cmp(a.x-p.x)*cmp(b.x-p.x)<=0&&cmp(a.y-p.y)*cmp(b.y-p.y)<=0; } inline int chk(point p){//-1:轮廓上 1:多边形内 0:多边形外 int cnt=0,d1,d2,k; for(int i=1;i<=n;++i){ if(onseg(p,a[i],a[i+1]) return -1; k=cmp((a[i+1]-a[i])*(p-a[i])); d1=cmp(a[i].y-p.y);d2=cmp(a[i+1].y-p.y); if(k>0&&d1<=0&&d2>0) ++cnt; if(k<0&&d2<=0&&d1>0) --cnt; } return cnt?1:0; }
平面最近点对
时间复杂度:\(O(nlogn)\).
point a[N];int n; bool cmpx(point a,point b){ if(a.x!=b.x) return a.x<b.x; return a.y<b.y; } bool cmpy(point a,point b){ if(a.y!=b.y) return a.y<b.y; return a.x<b.x; } inline double sqr(int k){ return (double)(k*k); } inline double dis(point a,point b){ return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y)); } inline double md(int l,int r){ double ret=INF; if(r-l<=20){ for(int i=l;i<r;++i) for(int j=i+1;j<=r;++j) ret=min(ret,dis(a[i],a[j])); return ret; } int mid=(l+r)>>1; ret=min(md(l,mid),md(mid+1,r)); while(a[l].x+ret<a[mid].x) ++l; while(a[r].x-ret>a[mid].x) --r; sort(a+l,a+r+1,cmpy); for(int i=l;i<=r;++i) for(int j=min(i+6,r);j>i;--j) ret=min(ret,dis(a[i],a[j])); sort(a+l,a+r+1,cmpx); return ret; } inline double middis(){ sort(a+1,a+1+n,cmpx); return md(1,n); }
半平面交
\(f(x)=Ax+By+C.\\Ax+By+C\geq{0}:(-1,f(-1))->(1,f(1));\\Ax+By+C\leq{0}:(1,f(1))->(-1,f(-1)).\)
struct line{ point s,t;double an; }a[N],q[N]; int h,t,n; inline bool cmp(line a,line b){ if(fabs(a.an-b.an)>eps) return a.an<y.an; return (a.t-a.s)*(b.s-a.s)>0; } inline point inter(line a,line b){ double s1,s2,tmp;point ret; s1=(b.t-a.s)*(a.t-a.s); s2(a.t-a.s)*(b.s-a.s); tmp=s2/(s1+s2); ret.x=b.s.x+(b.t.x-b.s.x)*tmp; ret.y=b.s.y+(b.t.y-b.s.y)*tmp; return ret; } inline bool chk(point p,line l){ return (p-l.s)*(l.t-l.s)>0; } inline void hpi(){ int m=1; for(int i=1;i<=n;++i) a[i].an=atan2(a[i].t.y-a[i].s.y,a[i].t.x-a[i].s.x); sort(a+1,a+1+n,cmp); for(int i=2;i<=n;++i) if(fabs(a[i].an-a[i-1].an)>eps) a[++m]=a[i]; h=1;t=0; for(int i=1;i<=m;++i){ while(h<t&&chk(inter(q[t],q[t-1]),a[i]) --t; while(h<t&&chk(inter(q[h],q[h+1]),a[i]) ++h; q[++t]=a[i]; } while(h<t&&chk(inter(q[t],q[t-1]),q[h]) --t; while(h<t&&chk(inter(q[h],q[h+1]),q[t]) ++h; return t-h+1>=3; }
数论
FFT
时间复杂度:\(O(nlogn)\).
#define N 100005 typedef long long ld; const ld pi=acos(-1.0); struct cp{ ld x,y; cp() {} cp(ld x,ld y):x(x),y(y) {} friend cp operator + (cp a,cp b){ return cp(a.x+b.x,a.y+b.y); } friend cp operator - (cp a,cp b){ return cp(a.x-b.x,a.y-b.y); } friend cp operator * (cp a,cp b){ return cp(a.x*b.x-a.y*b.y,a.y*b.x+a.x*b.y); } }a[N],b[N],c[N]; namespace FFT{ const int F=1e5+10; cp w[2][F];int re[F],n; inline void init(int k){ k<<=1;n=1;while(n<k) n<<=1; for(int i=0;i<n;++i){ w[0][i]=cp(cos(pi*2/n*i),sin(pi*2/n*i)); w[1][i]=cp(w[0][i].x,-w[0][i].y); } k=0;while((1<<k)<n) ++k; for(int i=0,t;i<n;++i){ t=0; for(int j=0;j<k;++j) if(i&(1<<j)) t|=(1<<k-j-1); re[i]=t; } } inline void DFT(cp *a,int ty){ cp *o=w[ty]; for(int i=0;i<n;++i) if(i<re[i]) swap(a[i],a[re[i]]); cp tmp; for(int l=2,m;l<=n;l<<=1){ m=l>>1; for(cp *p=a;p!=a+n;p+=l){ for(int i=0;i<m;++i){ tmp=o[n/l*i]*p[i+m]; p[i+m]=p[i]-tmp;p[i]=p[i]+tmp; } } } if(ty) for(int i=0;i<n;++i) a[i].x/=(ld)(n),a[i].y/=(ld)(n); } } inline void Aireen(){ FFT::DFT(a,0);FFT::DFT(b,0); for(int i=0;i<FFT::n;++i) c[i]=a[i]*b[i]; FFT::DFT(c,1); }
乘法逆元
扩展欧几里得求逆元
因为\(ab\equiv1(mod\;p)\),所以设\(q\)满足\(ab+pq=1\),
则可以用扩展欧几里得求关于\(b,q\)的方程\(ab+pq=1\)的一组解,即求出\(b\).
inline int exgcd(int a,int b,int &x,int &y){ if(!b){ x=1;y=0;return a; } int ret=exgcd(b,a%b,y,x); y-=a/b*x;return ret; } inline int inver{ r=exgcd(a,p,b,q); if(r!=1) return -1; return b; }
费马小定理求逆元
因为\(a^{p-1}\equiv1(mod\;p)(gcd(a,p)=1)\),所以\(a\;\times\;a^{p-2}\equiv1(mod\;p)\),
则\(a^{p-2}\)为\(a\)在\(mod\;p\)意义下的逆元.
typedef long long ll; inline ll power(ll x,ll k){ ll ret=1; while(k){ if(k&1) ret=ret*x%p; x=x*x%p;k>>=1; } return ret; } inline ll inver{ return power(a,p-2); }
线性求逆元
\(re[i]\)表示\(i\)在\(mod\;p\)(\(p\)为质数)意义下的逆元。
\(re[i]=\begin{cases}1&i=1\\-p/i\;\times\;re[p\;mod\;i]&i>1\\\end{cases}\)
证明:
因为\((p/i)\;\times\;i+p\;mod\;i=p\),
所以\((p/i)\;\times\;i+p\;mod\;i\equiv0(mod\;p)\)
所以\((p/i)\;\times\;i\equiv-p\;mod\;i(mod\;p)\)
所以\(-p/i\;\equiv\;p\;mod\;i\times\;i^{-1}(mod\;p)\)
所以,
\(\begin{split} &-(p/i)\;\times\;re[p\;mod\;i]\\ \equiv&-(p/i)\times(p\;mod\;i)^{-1}\\ \equiv&(p\;mod\;i)\;\times\;i^{-1}\times(p\;mod\;i)^{-1}\\ \equiv&\;i^{-1}(mod\;p)\\ \end{split}\)
typedef long long ll; inline ll inver{ re[1]=1; for(ll i=2,mul;i<=a;++i){ re[i]=-p/i*re[p%i]%p; if(re[i]<0){ mul=(0-re[i])/p+1; re[i]=(re[i]+mul*p)%p; } } return re[a]; }
线性筛
积性函数(素数)
若\((a,b)=1\),则\(f(ab)=f(a)\;\times\;f(b)\).
欧拉筛法保证每个合数只会被其最小的素数筛掉,所以复杂度是线性的。
int p[N],n,cnt; bool b[N]; inline void prime(){ b[0]=b[1]=true; for(int i=2;i<=n;++i){ if(!b[i]) p[++cnt]=i; for(int j=1;j<=cnt&&i*p[j]<=n;++j){ b[i*p[j]]=true; if(!(i%p[j])) break; } } }
每次\(p[j]|i\)时跳出循环能保证每个合数只会被其最小的素数筛掉,因为\(i\;\times\;p[k](k>j)\)的最小素数为\(p[j]\)。
欧拉函数
欧拉函数\(\phi(x)\)的定义:小于等于\(x\)的正整数中与\(x\)互质的数的个数。
\(\phi(x)=\begin{cases}1&x=1\\x-1&x\;is\;prime\\x\prod_{i=1}^{k}(\frac{p_{i}-1}{p_{i}})&x=p_1^{a_1}\times{p_2^{a_2}}\times\dots\times{p_k^{a_k}}\\\end{cases}\)
证明:
如果\(n\)为某一素数\(p\),则\(\phi(p)=p-1\).
如果\(n\)为某一素数\(p\)的幂次\(p^a,\phi(p^a)=(p-1)\;\times\;p^{a-1}\).
欧拉函数是积性函数,即当\((a,b)=1\)时\(f(ab)=f(a)\;\times\;f(b)\).
若\(x=p_1^{a_1}\;\times\;p_2^{a_2}\;\times\dots\times\;p_k^{a_k}\),则\(\phi(x)=\prod_{i=1}^{k}(p_i-1)\;\times\;p_i^{a_i-1}=x\prod_{i=1}^{k}\frac{p_i-1}{p_i}\).
设\(p\)为\(x\)最小的质数,\(x'=x/p\),在线性筛中,\(x\)被筛\(p\;\times\;x'\)掉。
当\(x'\;mod\;p\not=0\)时,\(\phi(x)=p\;\times\;x'\times\;(\frac{p-1}{p})\prod_{i=1}^{k'}(\frac{p_i-1}{p_i})=(p-1)\;\;\times\;\phi(x')\);
当\(x'\;mod\;p=0\)时,\(\phi(x)=p\;\times\;x'\;\times\;\prod_{i=1}^{k'}(\frac{p_i-1}{p_i})=p\;\times\;\phi(x')\).
int p[N],phi[N],n,cnt; bool b[N]; inline void prime(){ b[0]=b[1]=true;phi[1]=1; for(int i=2;i<=n;++i){ if(!b[i]){ p[++cnt]=i;phi[i]=i-1; } for(int j=1;j<=cnt&&i*p[j]<=n;++j){ b[i*p[j]]=true; if(!(i%p[j])){ phi[i*p[j]]=p[j]*phi[i];break; } phi[i*p[j]]=(p[j]-1)*phi[i]; } } }
莫比乌斯函数
莫比乌斯函数\(\mu(x)\)的定义:
\(\mu(x)=\begin{cases}1&x=1\\(-1)^{k}&x=p_{1}^{a_{1}}p_{2}^{a_{2}}\;\dots\;p_{k}^{a_{k}}(a_{i}=1)\\0&x=p_{1}^{a_{1}}p_{2}^{a_{2}}\;\dots\;p_{k}^{a_{k}}(max\{a_{i}\}>1)\end{cases}\)
显然当\(x\)是质数时,\(\mu(x)=-1\);
当\(x\)不是质数时,设\(p\)为\(x\)最小的质数,\(x'=x/p\),在线性筛中,\(x\)被筛\(p\;\times\;x'\)掉。
当\(x'\;mod\;p\not=0\)时,当\(\mu(x')\not=0\)时,显然\(a_{i}=1,\mu(x)=-\mu(x')\);
\(\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\)当\(\mu(x')=0\)时,\(\mu(x)=0\),即\(\mu(x)=-\mu(x')\);
当\(x'\;mod\;p=0\)时,显然\(max\{a_{i}\}>1\),\(\mu(x)=0\)。
int p[N],mu[N],n,cnt; bool b[N]; inline void prime(){ b[0]=b[1]=true;mu[1]=1; for(int i=2;i<=n;++i){ if(!b[i]){ p[++cnt]=i;mu[i]=-1; } for(int j=1;j<=cnt&&i*p[j]<=n;++j){ b[i*p[j]]=true; if(!(i%p[j])){ mu[i*p[j]]=0;break; } mu[i*p[j]]=-mu[i]; } } }
高斯消元
时间复杂度:\(O(n^3)\).
#define N 101 #define eps 1e-13 double a[N][N],ans[N]; int n;bool b[N]; inline void gauss(int n){ int m=0;double tmp; memset(b,0,sizeof(b)); for(int i=0;i<n;++i){ for(int j=m;j<n;++j){ if(fabs(a[j][i])>eps){ for(int k=i;k<=n;++k) swap(a[m][k],a[j][k]); break; } } if(fabs(a[m][i])<eps) continue; for(int j=0;j<n;++j) if(j!=m&&fabs(a[j][i])>eps){ tmp=a[j][i]/a[m][i]; for(int k=i;k<=n;++k) a[j][k]-=tmp*a[m][k]; } b[i]=true;++m; } for(int i=0;i<n;++i) if(b[i]) for(int j=0;j<n;++j) if(fabs(a[j][i])>eps){ ans[i]=a[j][n]/a[j][i];break; } }
扩展欧几里得
求二元一次不定方程\(ax+by=gcd(a,b)\)的一组解。
当\(b=0\)时,有一组解\(x=1,y=0\);
当\(b>0\)时,因为\(gcd(a,b)=gcd(b,a\;mod\;b)\),
所以设\(x',y'\)满足\(bx'+(a\;mod\;b)y'=gcd(a,b)\),
则\(bx'+(a-a/b\;\times\;b)y'=gcd(a,b)\),
整理得\(ay'+b(x'-a/b\;\times\;y')=gcd(a,b)\)。
所以\(x=y',y=x'-a/b\;\times\;y'\)。
就可以在求\(gcd\)的过程中得到一组解。
inline int exgcd(int a,int b,int &x,int &y){ if(!b){ x=1;y=0;return a; } else{ int ret=exgcd(b,a%b,y,x); y-=a/b*x;return ret; } }
BSGS
求最小的\(x\)使得\(a^{x}\;\equiv\;b(mod\;p),p\)为质数。
令\(s=\sqrt{p}\),则\(x=y\;\times\;s+r\;(0\;\leq\;r<s)\),即\(a^{x}=a^{y\;\times\;s}\;\times\;a^{r}\)。
将所有的\(a^{r}\)用\(map\)存起来,从小到大枚举\(y\),直到\(b/a^{y\;\times\;s}\)是某个\(a^{r}\),此时答案为\(y\;\times\;s+r\)。
typedef long long ll; map<ll,ll> m; inline ll po(ll x,ll k,ll p){ ll ret=1; while(k){ if(k&1) ret=ret*x%p; x=x*x%p;k>>=1; } return ret; } inline ll bsgs(ll a,ll b,ll p){ if(gcd(a,p)!=1) if(b) return -1; else return 1; ll s=sqrt(p),k=1,x=1,y; while(s*s<=p) ++s; for(ll i=0;i<s;++i){ if(!m[k]) m[k]=i; k=k*a%p; } for(ll i=0;i<s;++i){ y=b*po(x,p-2,p)%p; if(m.count(y)) return i*s+m[y]; x=x*k%p; } return -1; }
中国剩余定理
求方程组\(x\;\equiv\;a_i(mod\;m_i)(i\in[1,n])\)的解\(x\),其中\(m_i\)两两互质.
一般版本
令\(M_i=\prod_{j\not=i}m_j\),则\((M_i,m_i)=1\),所以存在\(M_ix_i+m_iy_i=1\).
(\(x_i\)为\(M_i\)在模\(m_i\)意义下的逆元)
令\(e_i=M_ix_i\),则\(e_i\equiv\begin{cases}0(mod\;m_j)&j\not=i\\1(mod\;m_j)&j=i\\\end{cases}\)
所以\(\sum_{i=1}^{n}e_ia_i\)是方程的一个解.
在\([0,\prod_{i=1}^{n}m_i)\)中只有唯一解,所以将求出的解对\(\prod_{i=1}^{n}m_i\)取模即可.
闫神
记\(q_{i,j}\)表示\(m_i\)在模\(m_j\)意义下的逆元.
\(x\equiv\sum_{i=1}^{n}(a_i\;\times\;\prod_{j=1}^{n}(m_j\;\times\;q_{j,i}))(mod\;\prod_{i=1}^{n}m_i)\)
不互质
若要合并\(x\;\equiv\;a_i(mod\;m_i),x\;\equiv\;a_j(mod\;m_j)\),
设\(x=k_im_i+a_i=k_jm_j+a_j\),则\(k_im_i-k_jm_j=a_j-a_i\).
用\(exgcd\)求出\(k_i\)(注意无解的情况),把两个方程合并为\(x\;\equiv\;k_im_i+a_i(mod\;lcm(m_i,m_j))\).
欧拉定理
若\(a,p\;\in\;N^{+},(a,p)=1\),则\(a^{\phi(p)}\;\equiv\;1(mod\;p)\).
阶
使得\(a^x\;\equiv\;1(mod\;p)\)的最小正整数\(x\)称为\(a\)模\(p\)的阶,记为\(ord_pa\).
实现
找一个数的阶可以暴力求解,原根为\(\phi(p)\)的因数.
原根
原根:\(ord_pa=\phi(p)\)时,称\(a\)是\(p\)的原根.
\(a^1,a^2,...,a^{\phi(p)}\)在模p意义下互不相同.
如果\(p\)有原根,那么原根个数为\(\phi(phi(p))\).
lucas定理
求\(c_n^m\;mod\;p\).
将\(n,m\)写成\(p\)进制:\(n=a_0p^0\;\times\;a_1p^1\;\times\;...\;\times\;a_kp^k,m=b_0p^0\;\times\;b_1p^1\;\times\;...\;\times\;b_kp^k.\)
则\(C_{n}^{m}\;\equiv\;\prod\;C_{a_i}^{b_i}(mod\;p)\).
所以\(C_n^m\;\equiv\;C_{n\;mod\;p}^{m\;mod\;p}\;\times\;C_{n/p}^{m/p}\)
斐波那契数列
\(fib(n+m)=fib(n+1)\;\times\;fib(m)+fib(n)\;\times\;fib(m-1).\)
\(gcd(fib(i),fib(j))=fib(gcd(i,j))\).
字符串算法
字符串匹配
KMP
时间复杂度:\(O(n)\)
#define N 1000005 int nxt[N],m,n; char a[N],b[N]; inline void get_nxt(){ for(int i=2,j=0;i<=m;++i){ while(j&&b[i]!=b[j+1]) j=nxt[j]; if(b[i]==b[j+1]) ++j; nxt[i]=j; } } inline void kmp(){ for(int i=1,j=0;i<=n;++i){ while(j&&a[i]!=b[j+1]) j=nxt[j]; if(a[i]==b[j+1]) ++j; if(j==m) printf("%d\n",i-m+1); } }
扩展KMP
求一个串对于另一个串的每个后缀的\(LCP\)(最长公共前缀).
实现
类似\(KMP\)的思想处理\(a,b\)串.
先求出\(b\)串与自己的每个后缀的\(LCP\),再用类似的方法求出\(b\)串与\(a\)串的每个后缀的\(LCP\).
设当前处理到\(i\),已经处理出\(g[1...i-1]\),\(k\)满足\(k+g[k]-1\)最大,即被匹配到的范围最大.
因为\(b[1...g[k]]=b[k...k+g[k]-1]\),所以\(b[i-k+1...g[k]]=b[i...k+g[k]-1]\).
如果\(i+g[i-k+1]-1<k+g[k]+1\),那么\(g[i]=g[i-k+1]\),
否则暴力匹配\(b[k+g[k]...n]和b[k+g[k]-i+1...n]\).
#define N 1000005 #define M 1000005 int f[N],g[M],n,m; char a[N],b[M]; void exkmp(){ for(int i=1;i<m;++i) if(b[i]!=b[i+1]){ g[2]=i-1;break; } if(!g[2]) g[2]=n-1; for(int i=3,j,k=2,l;i<=m;++i){ l=k+g[k]-1; if(g[i-k+1]<l-i) g[i]=g[i-k+1]; else{ for(j=max(1,l-i+2;j+i-1<=m;++j) if(b[j]!=b[j+i-1]) break; g[i]=j-1;k=i; } } for(int i=1;i<=n&&i<=m;++i) if(a[i]!=b[i]){ f[1]=i-1;break; } if(!f[1]) f[1]=min(n,m); for(int i=2,j,k=1,l;i<=n;++i){ l=k+f[k]-1; if(g[i-k+1]<l-i) f[i]=g[i-k+1]; else{ for(j=max(1,l-i+2;i+j-1<=m&&i+j-1<=n;++j) if(b[j]!=a[j+i-1]) break; f[i]=j-1;k=i; } } }
trie树
时间复杂度:\(O(\sum|S_i|)\)
#define N 100005 struct trie{ int chl[26];bool b; }tr[L]; int cnt; inline void insert(char s[]){ int u=0,l=strlen(s+1); for(int i=1;i<=l;++i){ if(!tr[u].chl[s[i]-'a']) tr[u].chl[s[i]-'a']=++cnt; u=tr[u].chl[s[i]-'a']; } tr[u].b=true; } inline bool find_pre(char s[]){ int u=0,l=strlen(s+1); for(int i=1;i<=l;++i){ if(!tr[u].chl[s[i]-'a']) return false; u=tr[u].chl[s[i]-'a']; } return true; }
AC自动机
#define N 105 #define T 10005 #define M 1350005 struct trie{ int chl[26],nxt;bool b; }tr[T]; int tot[T],cnt; queue<int> q; inline void insert(char s[]){ int u=0,l=strlen(s+1); for(int i=1;i<=l;++i){ if(!tr[u].chl[s[i]-'a']) tr[u].chl[s[i]-'a']=++cnt; u=tr[u].chl[s[i]-'a']; } tr[u].b=true; } inline void get_nxt(){ for(int i=0;i<26;++i) if(tr[0].chl[i]) q.push(tr[0].chl[i]); while(!q.empty()){ int u=q.front();q.pop(); for(int i=0,j,c;i<26;++i){ if(c=tr[u].chl[i]){ q.push(c);j=tr[u].nxt; while(j&&!tr[j].chl[i]) j=tr[j].nxt; tr[c].nxt=tr[j].chl[i]; } } } } //统计每个字符串在文章s中出现次数 inline void tot(char s[]){ int l=strlen(s+1); for(int i=1,j=0;i<=l;++i){ while(j&&!tr[j].chl[s[i]-'a']) j=tr[j].nxt; if(tr[j].chl[s[i]-'a']) j=tr[j].chl[s[i]-'a']; if(j) ++t[j]; } for(int i=cnt;i;--i) if(tr[i].nxt) t[tr[i].nxt]+=t[i]; }
字典序
后缀数组
时间复杂度:\(O(nlogn)\)
#define N 200005 int a[N],sa[N],rk[N],ht[N],fir[N],sec[N],bu1[N],bu2[N],tmp[N],m;//第i小 inline void getSA(){ memset(bu1,0,sizeof(bu1)); for(int i=1;i<=m;++i) ++bu1[a[i]]; for(int i=1;i<=m;++i) bu1[i]+=bu1[i-1]; for(int i=m;i;--i) sa[bu1[a[i]]--]=i; rk[sa[1]]=1; for(int i=2;i<=m;++i){ rk[sa[i]]=rk[sa[i-1]]; if(a[sa[i]]!=a[sa[i-1]]) ++rk[sa[i]]; } for(int t=1;rk[sa[m]]<m;t<<=1){ memset(bu1,0,sizeof(bu1)); memset(bu2,0,sizeof(bu2)); for(int i=1;i<=m;++i){ ++bu1[fir[i]=rk[i]]; ++bu2[sec[i]=((i+t>m)?0:rk[i+t])]; } for(int i=1;i<=m;++i) bu2[i]+=bu2[i-1]; for(int i=m;i;--i) tmp[bu2[sec[i]]--]=i; for(int i=1;i<=m;++i) bu1[i]+=bu1[i-1]; for(int i=m;i;--i) sa[bu1[fir[tmp[i]]]--]=tmp[i]; rk[sa[1]]=1; for(int i=2;i<=m;++i){ rk[sa[i]]=rk[sa[i-1]]; if(fir[sa[i]]!=fir[sa[i-1]]||sec[sa[i]]!=sec[sa[i-1]]) ++rk[sa[i]]; } } for(int i=1,j,k=0;i<=m;++i) { if(k) --k; j=sa[rk[i]-1]; while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k]) ++k; ht[rk[i]]=k; } }
回文串
manacher
时间复杂度:\(O(n)\)
#define N 200005 using namespace std; int r[N],m,n,mx,id,ans; char a[N]; inline int manacher(){ for(int i=n;i;--i){ a[i<<1]=a[i]; a[i<<1|1]='#'; } n=n<<1|1;mx=id=0; a[0]='$';a[1]=a[n+1]='#'; for(int i=1;i<=n;++i){ r[i]=i<mx?min(r[(id<<1)-i],mx-i):1; while(a[i+r[i]]==a[i-r[i]]) ++r[i]; if(i+r[i]>mx) mx=i+r[i],id=i; ans=max(ans,r[i]-1); } return ans; }
博弈论
Nim Game
n堆石子, 两个人轮流操作, 每次可从任意一堆中取出任意多>0的石子, 无法操作者输.
- 结论 : 将所有石子数异或起来, 结果不为\(0\) , 先手胜; 反之, 先手负.
- 证明 :
- 石子数都为 \(0\) 为必败态.
- 若当前异或和为 \(0\) , 则无论怎么取, 取后异或和不为 \(0\) .
- 若当前异或和 \(k\) (设最高位为 \(x\) ) 不为 \(0\) , 则必存在石子数 \(a_i\) 中位 \(x\) 为 \(1\) , 此时 \(a_i\;xor\;k<a_i\) .
即取 \(a_i\;xor\;k\) 可使异或和为 \(0\) .
Nimk Game
n堆石子, 两个人轮流操作, 每次可从任意\(\small{\leq}\)k堆中取出任意多>0的石子, 无法操作者输.
- 结论 : 将所有石子数的每一位异或起来 \(mod\;(k+1)\) , 若结果都为 \(0\) , 先手负; 反之, 先手胜.
- 证明 :
- 石子数都为 \(0\) 为必败态.
- 若当前每一位的异或和都为 \(0\) , 则无论怎么取, 取后必存在某位的异或和不为 \(0\).
- 若当前存在某位的异或和不为 \(0\) , 从高位往低位确定每堆石子取的个数.
记 \(m\) 为当前位取 \(0,1\) 都可的堆数 ( 如果某为 \(1\) 的位取 \(0\) , 则比其低的位可随意取值 ) , \(n\) 为这一位除这 \(m\) 堆外, 当前位为 \(1\) 的堆数 \(mod\;(k+1)\) 的值.
若 \(n+m\leq{k}\) , 则直接使这 \(n\) 堆此位取 \(0\) , 其他不变 , \(m=n+m\) 即可;
否则 , \(n+m>k\) 即 \(n+m\geq{k+1}\) , 此时 \(n\geq{k+1-m}\) , 在这 \(m\) 堆 \(k+1-m\) 位取 \(1\) , 其余取 \(0\) ,其他不变即可.
Bash Game
n堆石子,两个人轮流操作,每次可从任意一堆中取出[1,m]个石子,无法操作者输.
-
结论 : 将所有石子数 \(mod\;(m+1)\) 异或起来,结果不为\(0\) , 先手胜; 反之, 先手负.
-
证明 :
- 石子数都为 \(0\) 为必败态.
- 若当前异或和为 \(0\) , 则无论怎么取, 取后异或和不为 \(0\) .
- 若当前异或和 \(k\) (设最高位为 \(x\) ) 不为 \(0\) , 则必存在石子数 \(a_i\;mod\;(m+1)\) 中位 \(x\) 为 \(1\) , 此时 \(a_i\;mod\;(m+1)\;xor\;k<a_i\) .
即取 \(a_i\;mod\;(m+1)\;xor\;k\) 可使异或和为 \(0\) .
Staircase Nim
n阶台阶, 每次可以从一个台阶上拿掉任意数量石子放到下一层台阶, 无法操作者输 ( 第1级可以往地面上放 ) .
- 结论 : 将所有奇数级异或起来, 结果不为\(0\) , 先手胜; 反之, 先手负.
- 证明 :
- 台阶上石子数都为 \(0\) 为必败态.
- 若当前奇数级异或和为 \(0\) , 则无论是移奇数级到偶数级还是移偶数级到奇数级, 取后异或和不为 \(0\) .
- 若当前奇数级异或和为 \(0\) , 则移奇数级到偶数级后, 异或和不为 \(0\) .
STL
set
insert(x) erase(x) clear() count(x) find(value) //找不到返回end (*set0.lower_bound(x)) for(set<type>::iterator i=set0.begin();i!=setOfStr.end();++i) // the element (*i) set<int,less<int> > 升序 set<int,greater<int> > 降序
pair
pair<int,int> > a; a=make_pair(v1,v2); a.first a.second
- 有比较操作
map
map<key,value> at(key) count(key) find(key) insert(pair)
bitset
bitset<N> set0; &=,|=,^=,<<=,>>=,==,!= &,|,^,<<,>>,~ set() //将所有位全部设成 1 reset() //将所有位全部设成0 flip(); //将所有位翻转(0变成1,1变成0) set(size_t pos, bool val = true) //将第 pos 位设为 val reset (size_t pos) //将第 pos 位设成 0 flip(size_t pos) //翻转第 pos 位 set0[pos] count() //计算 1 的个数 size () //返回总位数 test(size_t pos) //测试第 pos 位是否为 1 any() //判断是否有某位为1 none() //判断是否全部为0
queue
inline bool bfs(int u){ memset(dep,0,sizeof(dep)); q.push(u);dep[s]=1; while(!q.empty()){ u=q.front();q.pop(); for(int i=g[u];i;i=e[i].nxt) if(e[i].f>0&&!dep[e[i].to]){ q.push(e[i].to); dep[e[i].to]=dep[u]+1; } } return dep[t]; }
priority_queue
默认大根堆,重载<。
priority_queue<int,vector<int>,greater<int> > q;//小根堆
vector
for(vector<int>::iterator i=v.begin();i!=v.end();++i) for(vector<int>::reverse_iterator i=v.rbegin();i!=v.rend();++i) find(begin,end,value) //找不到返回end
二维动态数组
int (*a)[m]=new int[n][m]; delete []a; vector<vector<int> > a(n); for(int i=0;i<n;++i) a[i].resize(m);
list
//unique和sort可自定义cmp auto cmp1=[](const int& a, const int& b)->bool{return a%K==b%K;}; list1.unique(cmp1); auto cmp2=[](const int&a, const int& b)->bool{return a>b;}; list1.sort(cmp2);//numbers.sort(greater<>());升序
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· .NET 9 new features-C#13新的锁类型和语义
· 《HelloGitHub》第 106 期
· Spring AI + Ollama 实现 deepseek-r1 的API服务和调用
· 数据库服务器 SQL Server 版本升级公告
· 深入理解Mybatis分库分表执行原理
· 使用 Dify + LLM 构建精确任务处理应用