板子

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

  1. 根据条件表达式建边:\((x,y)\)表示如果选了\(x\)必须选\(y\)
  2. 缩环;
  3. 判断是否可行;
  4. 根据缩完环的图反向建边;
  5. 拓扑排序进行染色(\(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]\),将其拆成三条边:

  1. \((S,v)=l\);
  2. \((u,v)=u-l\);
  3. \((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\)有可能有多条边,合并这些边,节省空间.

  1. 无源汇
    可行流
    \(S->T\)的最大流,从\(S\)出发的边全部满流则可行,因为说明所有边的下界均已满足.每条边的实际流为自由流+流量下界.
  2. 有源汇
    可行流
    加一条边\((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\) , 先手胜; 反之, 先手负.
  • 证明 :
  1. 石子数都为 \(0\) 为必败态.
  2. 若当前异或和为 \(0\) , 则无论怎么取, 取后异或和不为 \(0\) .
  3. 若当前异或和 \(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\) , 先手负; 反之, 先手胜.
  • 证明 :
  1. 石子数都为 \(0\) 为必败态.
  2. 若当前每一位的异或和都为 \(0\) , 则无论怎么取, 取后必存在某位的异或和不为 \(0\).
  3. 若当前存在某位的异或和不为 \(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\) , 先手胜; 反之, 先手负.

  • 证明 :

  1. 石子数都为 \(0\) 为必败态.
  2. 若当前异或和为 \(0\) , 则无论怎么取, 取后异或和不为 \(0\) .
  3. 若当前异或和 \(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\) , 先手胜; 反之, 先手负.
  • 证明 :
  1. 台阶上石子数都为 \(0\) 为必败态.
  2. 若当前奇数级异或和为 \(0\) , 则无论是移奇数级到偶数级还是移偶数级到奇数级, 取后异或和不为 \(0\) .
  3. 若当前奇数级异或和为 \(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<>());升序
posted @ 2021-11-28 19:48  Aireen_Ye  阅读(151)  评论(0编辑  收藏  举报
底部 顶部 留言板 归档 标签
Der Erfolg kommt nicht zu dir, du musst auf den Erfolg zugehen.