ZCETHAN の板子们

CSP 赛前的模板复习。其中代码一般以洛谷模板题为基础。变量类型一般为 int,数据范围一般为 1 0 5 10^5 105,算法范围一般为 提高级

具体还是看代码。

数据结构

倍增表(ST 表)

Q Q Q 次询问长度为 n n n 区间内的最大值。

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e5+10;
int dp[MAXN][20],a[MAXN],n;
void init(){
	for(int i=1;i<=n;i++)
		dp[i][0]=a[i];
	for(int j=1;j<20;j++)
		for(int i=1;i+(1<<(j-1))<=n;i++)
			dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int ask(int l,int r){
	int k=log2(r-l+1);
	return max(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main()
{
	scanf("%d",&n);
	int Q,l,r;scanf("%d",&Q);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	init();
	while(Q--){
		scanf("%d%d",&l,&r);
		printf("%d\n",ask(l,r));
	}
}

线段树

区间加,区间求和。

#include<bits/stdc++.h>
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=1e5+10;
struct Tree{
	int l,r;
	ll sum,inc;
}tr[MAXN<<2];
#define ls i<<1
#define rs i<<1|1
ll a[MAXN];
void build(int i,int l,int r){
	tr[i].l=l;tr[i].r=r;tr[i].inc=0;
	if(l==r){tr[i].sum=a[l];return;}
	int mid=l+r>>1;
	build(ls,l,mid);build(rs,mid+1,r);
	tr[i].sum=tr[ls].sum+tr[rs].sum;
}
void pushdown(int i){
	if(!tr[i].inc) return;
	tr[i].sum+=tr[i].inc*(tr[i].r-tr[i].l+1);
	tr[ls].inc+=tr[i].inc;
	tr[rs].inc+=tr[i].inc;
	tr[i].inc=0;
}
void upd(int i,int l,int r,ll v){
	if(tr[i].l==l&&tr[i].r==r){
		tr[i].inc+=v;return;
	}tr[i].sum+=(r-l+1)*v;
	int mid=tr[i].l+tr[i].r>>1;
	if(r<=mid) upd(ls,l,r,v);
	else if(l>mid) upd(rs,l,r,v);
	else upd(ls,l,mid,v),upd(rs,mid+1,r,v);
}
ll query(int i,int l,int r){
	if(tr[i].l==l&&tr[i].r==r)
		return tr[i].sum+tr[i].inc*(r-l+1);
	pushdown(i);
	int mid=tr[i].l+tr[i].r>>1;
	if(r<=mid) return query(ls,l,r);
	else if(l>mid) return query(rs,l,r);
	else return query(ls,l,mid)+query(rs,mid+1,r);
}
int main()
{
	int n,m,x,y,op;ll k;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	build(1,1,n);
	while(m--){
		scanf("%d%d%d",&op,&x,&y);
		if(op==1){
			scanf("%lld",&k);
			upd(1,x,y,k);
		}else printf("%lld\n",query(1,x,y));
	}
}

树状数组

单点修改,区间求和。

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=5e5+10;
ll tr[MAXN];
int n;
int lbt(int x){return x&(-x);}
void upd(int x,ll v){for(;x<=n;x+=lbt(x))tr[x]+=v;}
ll ask(int x){ll ret=0;for(;x;x-=lbt(x))ret+=tr[x];return ret;}
int main()
{
	int m;ll k;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&k),upd(i,k);
	int x,y,op;
	while(m--){
		scanf("%d%d",&op,&x);
		if(op==1){
			scanf("%lld",&k);
			upd(x,k);
		}else{
			scanf("%d",&y);
			printf("%lld\n",ask(y)-ask(x-1));
		}
	}
	return 0;
}

SAM

求出 S S S 的所有出现次数不为 1 1 1 的子串的出现次数乘上该子串长度的最大值。

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e6+10;
int fa[MAXN<<1],ch[MAXN<<1][30],tot=1;
int siz[MAXN<<1],lst=1,len[MAXN<<1];
void ins(int c){
	int p=lst,q=++tot;lst=q;
	len[q]=len[p]+1;siz[q]=1;
	while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
	if(!p) fa[q]=1;
	else{
		int x=ch[p][c];
		if(len[p]+1==len[x]) fa[q]=x;
		else{
			int cl=++tot;
			fa[cl]=fa[x];
			fa[x]=fa[q]=cl;
			len[cl]=len[p]+1;
			memcpy(ch[cl],ch[x],sizeof(ch[x]));
			while(p&&ch[p][c]==x) ch[p][c]=cl,p=fa[p];
		}
	}
}
char s[MAXN];
vector<int> e[MAXN<<1];
ll ans=0;
void dfs(int x){
	for(int s:e[x]){
		dfs(s);siz[x]+=siz[s];
	}if(siz[x]>1)ans=max(ans,1ll*siz[x]*len[x]);
}
int main()
{
	scanf("%s",s+1);
	int len=strlen(s+1);
	for(int i=1;i<=len;i++)
		ins(s[i]-'a');
	for(int i=1;i<=tot;i++)
		e[fa[i]].push_back(i);
	dfs(1);
	printf("%lld\n",ans);
	return 0;
}

笛卡尔树

构建一颗笛卡尔树。

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e7+10;
#define gc getchar()
int read(){
	int w=0;char c=gc;
	while(!isdigit(c))c=gc;
	while(isdigit(c))w=w*10+c-'0',c=gc;
	return w;
}//tuu
int ls[MAXN],rs[MAXN];
int n,p[MAXN],stk[MAXN],top;
inline void build(){
	for(int i=1;i<=n;i++){
		while(top&&p[stk[top]]>p[i]) top--;
		ls[i]=rs[stk[top]];
		rs[stk[top]]=i;
		stk[++top]=i;
	}
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
		p[i]=read();
	build();
	ll ansl=0,ansr=0;
	for(int i=1;i<=n;i++)
		ansl^=1ll*i*(ls[i]+1),
		ansr^=1ll*i*(rs[i]+1);
	printf("%lld %lld\n",ansl,ansr);
	return 0;
}

Treap

插入 x x x
删除 x x x 数(若有多个相同的数,因只删除一个)
查询 x x x 数的排名(排名定义为比当前数小的数的个数 + 1 +1 +1
查询排名为 x x x 的数
x x x 的前驱(前驱定义为小于 x x x,且最大的数)
x x x 的后继(后继定义为大于 x x x,且最小的数)

#include<bits/stdc++.h>
#define ll long long
#define inf 1e8
using namespace std;
const int MAXN=1e5+10;
struct node{
	int ls,rs;
	int key,val,siz,cnt;
}tr[MAXN];
int root,idx;
inline void pushup(int p){
	tr[p].siz=tr[tr[p].ls].siz+tr[tr[p].rs].siz+tr[p].cnt;
}
int getnode(int key){
	tr[++idx].key=key;
	tr[idx].val=rand();
	tr[idx].cnt=tr[idx].siz=1;
	return idx;
}
void zig(int &p){
	int q=tr[p].ls;
	tr[p].ls=tr[q].rs;tr[q].rs=p;p=q;
	pushup(tr[p].rs);pushup(p);
}
void zag(int &p){
	int q=tr[p].rs;
	tr[p].rs=tr[q].ls;tr[q].ls=p;p=q;
	pushup(tr[p].ls);pushup(p);
}
void build(){
	getnode(-inf);getnode(inf);
	root=1;tr[1].rs=2;
	pushup(root);
	if(tr[1].val<tr[2].val)
		zag(root);
}
void ins(int &p,int key){
	if(!p) p=getnode(key);
	else if(tr[p].key==key) tr[p].cnt++;
	else if(tr[p].key>key){
		ins(tr[p].ls,key);
		if(tr[tr[p].ls].val>tr[p].val) zig(p);
	}else{
		ins(tr[p].rs,key);
		if(tr[tr[p].rs].val>tr[p].val) zag(p);
	}pushup(p);
}
void del(int &p,int key){
	if(!p) return;
	if(tr[p].key==key){
		if(tr[p].cnt>1) tr[p].cnt--;
		else if(tr[p].ls||tr[p].rs){
			if(!tr[p].rs||tr[tr[p].ls].val>tr[tr[p].rs].val)
				zig(p),del(tr[p].rs,key);
			else zag(p),del(tr[p].ls,key);
		}else p=0;
	}
	else if(tr[p].key>key) del(tr[p].ls,key);
	else del(tr[p].rs,key);
	pushup(p);
}
int grbk(int p,int key){
	if(!p) return 0;
	if(tr[p].key==key) return tr[tr[p].ls].siz+1;
	if(tr[p].key>key) return grbk(tr[p].ls,key);
	return tr[tr[p].ls].siz+tr[p].cnt+grbk(tr[p].rs,key);
}
int gkbr(int p,int rank){
	if(!p) return inf;
	if(rank<=tr[tr[p].ls].siz) return gkbr(tr[p].ls,rank);
	if(rank<=tr[tr[p].ls].siz+tr[p].cnt) return tr[p].key;
	return gkbr(tr[p].rs,rank-tr[tr[p].ls].siz-tr[p].cnt);
}
int prev(int p,int key){
	if(!p) return -inf;
	if(tr[p].key>=key) return prev(tr[p].ls,key);
	else return max(tr[p].key,prev(tr[p].rs,key));
}
int next(int p,int key){
	if(!p) return inf;
	if(tr[p].key<=key) return next(tr[p].rs,key);
	else return min(tr[p].key,next(tr[p].ls,key));
}
int main()
{
	build();
	int n,op,x;
	for(scanf("%d",&n);n--;){
		scanf("%d%d",&op,&x);
		if(op==1) ins(root,x);
		else if(op==2) del(root,x);
		else if(op==3) printf("%d\n",grbk(root,x)-1);
		else if(op==4) printf("%d\n",gkbr(root,x+1));
		else if(op==5) printf("%d\n",prev(root,x));
		else printf("%d\n",next(root,x));
	}
}

Splay

插入 x x x
删除 x x x 数(若有多个相同的数,因只删除一个)
查询 x x x 数的排名(排名定义为比当前数小的数的个数 + 1 +1 +1
查询排名为 x x x 的数
x x x 的前驱(前驱定义为小于 x x x,且最大的数)
x x x 的后继(后继定义为大于 x x x,且最小的数)
强制在线

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=2e6+10;
struct Tree{int ch[2],cnt,siz,val,fa;}nd[MAXN];
int root,tot;
void pushup(int rt){
    nd[rt].siz=nd[nd[rt].ch[0]].siz+nd[nd[rt].ch[1]].siz+nd[rt].cnt;
}void clear(int rt){
    nd[rt].ch[0]=nd[rt].ch[1]=0;
    nd[rt].cnt=nd[rt].siz=nd[rt].val=nd[rt].fa=0;
}int chk(int rt){return nd[nd[rt].fa].ch[1]==rt;}
void rot(int rt){
	int p=nd[rt].fa,g=nd[p].fa,d=chk(rt);
	nd[g].ch[chk(p)]=rt;nd[rt].fa=g;
	nd[p].ch[d]=nd[rt].ch[d^1];
	nd[nd[rt].ch[d^1]].fa=p;
	nd[rt].ch[d^1]=p;nd[p].fa=rt;
	pushup(p);pushup(rt);
}
void splay(int rt,int gl){
    while(nd[rt].fa!=gl){
        int p=nd[rt].fa,g=nd[p].fa;
        if(g==gl) rot(rt);
        else if(chk(rt)==chk(p)) rot(p),rot(rt);
        else rot(rt),rot(rt);
    }if(!gl) root=rt;
}
void ins(int rt,int val,int f){
    if(!rt){
        rt=++tot;nd[rt].val=val;nd[rt].siz=1;nd[rt].cnt=1;
        nd[rt].fa=f;nd[f].ch[val>=nd[f].val]=rt;
        splay(rt,0);return;
    }if(nd[rt].val==val){
    	nd[rt].cnt++;splay(rt,0);
    	return;
	}
    int d=(nd[rt].val<=val);
    ins(nd[rt].ch[d],val,rt);
}
int rank(int rt,int val){
	int ret=1;
	while(rt){
		if(nd[rt].val==val){
			ret+=nd[nd[rt].ch[0]].siz;
			splay(rt,0);return ret;
		}else if(val<nd[rt].val) rt=nd[rt].ch[0];
		else ret+=nd[rt].cnt+nd[nd[rt].ch[0]].siz,rt=nd[rt].ch[1];
	}return ret;
}
int kth(int rt,int k){
    while(rt){
    	int lsiz=nd[nd[rt].ch[0]].siz;
    	if(k>=lsiz+1&&k<=lsiz+nd[rt].cnt){
    		splay(rt,0);return nd[rt].val;
		}else if(k<=lsiz) rt=nd[rt].ch[0];
		else k-=lsiz+nd[rt].cnt,rt=nd[rt].ch[1];
	}return nd[rt].val;
}
void del(int rt,int x){
    if(nd[rt].val==x){splay(rt,0);
        if(nd[rt].cnt>1) nd[rt].cnt--;
        else if(nd[rt].ch[0]){
            int ls=nd[rt].ch[0];
            while(nd[ls].ch[1]) ls=nd[ls].ch[1];
            splay(ls,rt);nd[nd[rt].ch[1]].fa=ls;
            nd[ls].ch[1]=nd[rt].ch[1];root=ls;
			pushup(ls);clear(rt);nd[ls].fa=0;
        }else root=nd[rt].ch[1],nd[root].fa=0,clear(rt);
		return;
    }del(nd[rt].ch[nd[rt].val<x],x);
}
int pre(int val){
	int rk=rank(root,val)-1;
	return kth(root,rk);
}
int suf(int val){
	int rk=rank(root,val+1);
	return kth(root,rk);
}
void debug(int rt){
	if(nd[rt].ch[0]) debug(nd[rt].ch[0]);
	printf("%d %d ",nd[rt].val,nd[rt].cnt);
	if(nd[rt].ch[1]) debug(nd[rt].ch[1]);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x);
        ins(root,x,0);
    }
    int opt,x,lst=0,ans=0;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&opt,&x);
        x^=lst;
        if(opt==1) ins(root,x,0);
        else if(opt==2) del(root,x);
        else if(opt==3) lst=rank(root,x),ans^=lst;
        else if(opt==4) lst=kth(root,x),ans^=lst;
        else if(opt==5) lst=pre(x),ans^=lst;
        else lst=suf(x),ans^=lst;
    }printf("%d\n",ans);
}

FHQ-Treap

插入 x x x
删除 x x x 数(若有多个相同的数,因只删除一个)
查询 x x x 数的排名(排名定义为比当前数小的数的个数 + 1 +1 +1
查询排名为 x x x 的数
x x x 的前驱(前驱定义为小于 x x x,且最大的数)
x x x 的后继(后继定义为大于 x x x,且最小的数)
强制在线

//先咕着,2021.10.22 补

Tarjan


由于 Tarjan 老爷子非常牛逼,他发明的几个算法大部分都要考,所以这里都放出来吧,也可以对比看看 Tarjan 是多作弊 算法之间的相似与不同。

缩点

vector<int> e[MAXN];
int dfn[MAXN],low[MAXN],tot;
int stk[MAXN],top,col[MAXN],scc;
void Tarjan(int x,int fa){
	dfn[x]=low[x]=++tot;
	stk[++top]=x;
	for(int s:e[x]){
		if(s==fa) continue;
		if(!dfn[s]) Tarjan(s,x),low[x]=min(low[x],low[s]);
		else low[x]=min(low[x],dfn[s]);
	}if(low[x]==dfn[x]){
		scc++;do{col[stk[top]]=scc;
		}while(stk[top--]!=x);
	}
}

点双

int dfn[MAXN],low[MAXN],tot;
int stk[MAXN],top,cnt;
vector<int> bcc[MAXN];
vector<int> e[MAXN];
void Tarjan(int x,int fa){
	dfn[x]=low[x]=++tot;
	stk[++top]=x;
	for(int s:e[x]){
		if(!dfn[s]){
			Tarjan(s,x);
			low[x]=min(low[x],low[s]);
			if(low[s]>=dfn[x]){
				cnt++;
				while(stk[top]!=s)
					bcc[cnt].push_back(stk[top--]);
				bcc[cnt].push_back(stk[top--]);
				bcc[cnt].push_back(x);
			}
		}else low[x]=min(low[x],dfn[s]);
	}
}

边双

//先咕着

割点

vector<int> e[MAXN];
int dfn[MAXN],low[MAXN],tot,res;
bool vis[MAXN],ans[MAXN];
void Tarjan(int x,int fa){
	dfn[x]=low[x]=++tot;vis[x]=1;
	int cnt=0;
	for(int i=0;i<e[x].size();i++){
		int s=e[x][i];
		if(!vis[s]){
			cnt++;Tarjan(s,x);
			low[x]=min(low[x],low[s]);
			if(x!=fa&&low[s]>=dfn[x])
				ans[x]=1;
		}else if(s!=fa)
			low[x]=min(low[x],dfn[s]);
	}if(x==fa&&cnt>=2)
		ans[x]=1;
}

割边

这也要板子?把上面的代码贺下来然后把根的特判以及 if 里的等于号删掉就可以了。

欧拉路径与欧拉回路


算法

SAM

上面放过了

KMP

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e6+10;
char s[MAXN],t[MAXN];
int nxt[MAXN],slen,tlen;
void getnxt(){
	int k=-1;nxt[0]=-1;
	for(int i=1;i<tlen;i++){
		while(~k&&t[k+1]!=t[i]) k=nxt[k];
		if(t[k+1]==t[i]) k++;
		nxt[i]=k;
	}
}
int main()
{
	scanf("%s%s",s,t);
	slen=strlen(s);tlen=strlen(t);
	getnxt();
	int j=-1;
	for(int i=0;i<slen;i++){
		while(~j&&t[j+1]!=s[i]) j=nxt[j];
		if(t[j+1]==s[i]) j++;
		if(j==tlen-1){
			printf("%d\n",i+1-tlen+1);
			j=nxt[j];
		}
	}
	for(int i=0;i<tlen;i++)
		printf("%d ",nxt[i]+1);
	return 0;
}

最小生成树

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e5+10;
struct Edge{
	int u,v;ll w;
	void input(){
		scanf("%d%d",&u,&v);
	}bool friend operator<(Edge a,Edge b){
		return a.w<b.w;
	}
}ed[MAXN];
int f[MAXN];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		ed[i].input();
	sort(ed+1,ed+1+m);
	for(int i=1;i<=n;i++)
		f[i]=i;
	ll ans=0;
	for(int i=1;i<=m;i++){
		int u=find(ed[i].u),v=find(ed[i].v);
		if(u==v) continue;
		ans+=ed[i].w;
		f[u]=v;
	}printf("%lld\n",ans);
}

最短路

Dijkstra

给定有向图和起点,求所有点的最短路。

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e5+10;
struct Edge{
	int to;ll w;Edge(){}
	Edge(int _to,ll _w){to=_to;w=_w;}
};vector<Edge> e[MAXN];
struct node{
	int x;ll dis;node(){}
	node(int _x,ll _dis){x=_x;dis=_dis;}
	bool friend operator<(node a,node b){return a.dis>b.dis;}
};priority_queue<node> q;
ll dis[MAXN];
int main()
{
	int n,m,st;
	scanf("%d%d%d",&n,&m,&st);
	for(int i=1;i<=m;i++){
		int u,v;ll w;
		scanf("%d%d%lld",&u,&v,&w);
		e[u].push_back(Edge(v,w));
	}
	for(int i=1;i<=n;i++)
		dis[i]=(1ll<<31)-1;
	q.push(node(st,0));
	dis[st]=0;
	while(!q.empty()){
		node cur=q.top();q.pop();
		if(dis[cur.x]<cur.dis) continue;
		for(auto s:e[cur.x]){
			node nxt=node(s.to,s.w+cur.dis);
			if(dis[nxt.x]>nxt.dis){
				dis[nxt.x]=nxt.dis;
				q.push(nxt);
			}
		}
	}for(int i=1;i<=n;i++)
		printf("%lld ",dis[i]);
	return 0;
}

bellman-ford

判断是否有负环

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e4+10;
struct Edge{
	int u,v;ll w;
	Edge(){}
	Edge(int _u,int _v,ll _w){
		u=_u;v=_v;w=_w;
	}
}e[MAXN];
ll dis[MAXN];
int f[MAXN];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void solve(){
	int n,m,tot=0;scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=m;i++){
		int u,v;ll w;scanf("%d%d%lld",&u,&v,&w);
		if(w>=0) e[++tot]=Edge(u,v,w),e[++tot]=Edge(v,u,w);
		else e[++tot]=Edge(u,v,w);
	}
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	for(int i=1;i<n;i++)
		for(int j=1;j<=tot;j++)
			if(dis[e[j].v]>dis[e[j].u]+e[j].w){
				dis[e[j].v]=dis[e[j].u]+e[j].w;
				if(find(e[j].v)!=find(e[j].u))
				f[find(e[j].v)]=find(e[j].u);
			}
	bool neg=0;
	for(int j=1;j<=tot;j++)
		if(dis[e[j].v]>dis[e[j].u]+e[j].w){
			if(find(e[j].v)!=find(1)||find(e[j].u)!=find(1))
				continue;
			neg=1;break;
		}
	puts(neg?"YES":"NO");
}
int main()
{
	int T;
	for(scanf("%d",&T);T--;)
		solve();
	return 0;
}
//这里注一下:因洛谷板题需求,要求负环与 1 联通,所以加了个并查集。

Floyd

求两两之间的最短路

int dp[MAXN][MAXN];

for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			dp[i][j]=min(dp[i][k]+dp[k][j],dp[i][j]);

二分图匈牙利算法

求二分图最大匹配

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=510;
int mp[MAXN][MAXN],mat[MAXN];
int n,m,E,vis[MAXN];
bool find(int x){
	for(int i=1;i<=m;i++){
		if(!mp[x][i]||vis[i]) continue;
		vis[i]=1;
		if(!mat[i]||find(mat[i])){
			mat[i]=x;
			return 1;
		}
	}return 0;
}
int main()
{
	scanf("%d%d%d",&n,&m,&E);
	for(int i=1,u,v;i<=E;i++){
		scanf("%d%d",&u,&v);
		mp[u][v]=1;
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		memset(vis,0,sizeof(vis)),
		ans+=find(i);
	printf("%d\n",ans);
	return 0;
}

倍增求 LCA

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=5e5+10;
int fa[MAXN][20],dep[MAXN];
vector<int> e[MAXN];
void dfs(int x,int fat){
	fa[x][0]=fat;
	for(int i=1;i<20;i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int s:e[x])
		if(s!=fat){
			dep[s]=dep[x]+1;
			dfs(s,x);
		}
}
int LCA(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	int dlt=dep[x]-dep[y];
	for(int i=19;i>=0;i--)
		if(dlt&(1<<i))
			x=fa[x][i];
	if(x==y) return x;
	for(int i=19;i>=0;i--)
		if(fa[x][i]!=fa[y][i])
			x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
int main()
{
	int n,m,s;
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		e[u].push_back(v);
		e[v].push_back(u);
	}dfs(s,0);
	while(m--){
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",LCA(x,y));
	}
	return 0;
}

Tarjan

数论

这部分代码比较简单少,但是定理与必要的证明比较重要。

欧拉函数

首先是欧拉定理的公式:
φ ( n ) = ∏ φ ( p i k i ) = ∏ ( 1 − 1 p i ) × p i k i = n × ∏ ( 1 − 1 p i ) \varphi(n)=\prod\varphi(p_i^{k_i})=\prod(1-\dfrac{1}{p_i})\times p_i^{k_i}=n\times \prod(1-\dfrac{1}{p_i}) φ(n)=φ(piki)=(1pi1)×piki=n×(1pi1)
于是我们就有:

ll PHI(ll x){
	ll ret=x;
	for(int i=1;i<=cnt&&pr[i]*pr[i]<=x;i++){
		if(x%pr[i]!=0) continue;
		while(x%pr[i]==0) x/=pr[i];
		ret=ret/pr[i]*(pr[i]-1);
	}
	if(x>1) ret=ret/x*(x-1);
	return ret;
}

当然前面先筛质数——
所以我们还有一种做法,在线筛的时候就顺便把 φ ( n ) \varphi(n) φ(n) 给求出来。

考虑线筛的过程,每个合数都只被其最小的质因子 p 1 p_1 p1 标记。我们设这个合数是 n n n,那么它是在 m × p 1 = n m\times p_1=n m×p1=n 的时候筛掉的。那想当然的根据欧拉函数的积性, φ ( n ) = φ ( m ) × φ ( p 1 ) = φ ( m ) × ( p 1 − 1 ) \varphi(n)=\varphi(m)\times \varphi(p_1)=\varphi(m)\times (p_1-1) φ(n)=φ(m)×φ(p1)=φ(m)×(p11)

但是还需要考虑的是 m % p 1 = 0 m\%p_1=0 m%p1=0 的情况。此时,我们发现其实 n n n 中的质因子与 m m m 是完全相同了。那我们回头看上面代码上面的式子,发现 ∏ \prod 后面的东西是一样的,所以最终只要在 φ ( m ) \varphi(m) φ(m) 的基础上乘上一个 p 1 p_1 p1 就可以了。

const int MAXN=1e6+10;
int pr[MAXN],p[MAXN],cnt,phi[MAXN];
void init(){
	phi[1]=1;
	for(int i=2;i<=MAXN-10;i++){
		if(!p[i]){phi[i]=i-1;pr[++cnt]=i;}
		for(int j=1;j<=cnt&&pr[j]*i<=MAXN-10;j++){
			p[i*pr[j]]=1;
			if(i%pr[j]) phi[i*pr[j]]=phi[i]*(pr[j]-1);
			else{phi[i*pr[j]]=phi[i]*pr[j];break;}
		}
	}
}

欧拉定理

a φ ( m ) ≡ 1 ( m o d    m ) a^{\varphi(m)}\equiv1(\mod m) aφ(m)1(modm)
这个东西,记住就好了。
反正证明看过一遍就忘了。
扩展的应该不考吧?

逆元

乘法逆元,三种求法。

  • 用费马小定理,由 a p − 1 ≡ 1 ( m o d    p ) a^{p-1}\equiv1(\mod p) ap11(modp) 得到 a p − 2 ≡ 1 a ( m o d    p ) a^{p-2}\equiv\dfrac{1}{a}(\mod p) ap2a1(modp),条件是 p p p 为质数。

  • 用扩展欧几里德求解,设逆元为 i n v inv inv,则有 a × i n v ≡ 1 ( m o d    p ) a\times inv\equiv1(\mod p) a×inv1(modp),展开后可以得到 i n v × a − k × p = 1 inv\times a-k\times p=1 inv×ak×p=1,相当于一个不定方程,可以用扩展欧几里德求出一组可行解。没有限制。

  • 用线性递推,首先有 i n v ( 1 ) = 1 inv(1)=1 inv(1)=1,然后求 i i i 的逆元。首先有 p ≡ 0 ( m o d    p ) p\equiv0(\mod p) p0(modp),我们试图把 p p p i i i 代入得到:
    k = ⌊ p i ⌋ k=\left\lfloor\dfrac{p}{i}\right\rfloor k=ip j = p % i j=p\%i j=p%i,那么有 p = k i + j p=ki+j p=ki+j,即 k i + j ≡ 0 ( m o d    p ) ki+j\equiv0(\mod p) ki+j0(modp)。此时,我们需要式子中出现 i n v ( i ) inv(i) inv(i),于是我们在式子两边同时乘上一个 i n v ( i ) inv(i) inv(i),得到 k + j × i n v ( i ) ≡ 0 ( m o d    p ) ⇒ j × i n v ( i ) ≡ − k ( m o d    p ) k+j\times inv(i)\equiv0(\mod p)\Rightarrow j\times inv(i)\equiv-k(\mod p) k+j×inv(i)0(modp)j×inv(i)k(modp)。然后我们发现这个 j j j 很难搞,其实不然,容易发现 j j j 是小于 i i i 的,所以 i n v ( j ) inv(j) inv(j) 是已知的,我们直接把 j j j 除过去就可以了。 i n v ( i ) ≡ − k × i n v ( j ) ( m o d    p ) inv(i)\equiv-k\times inv(j)(\mod p) inv(i)k×inv(j)(modp),然后把 k k k j j j 代入就可以了。

inv[1]=1;
for(int i=2;i<=MAXN-10;i++)
	inv[i]=((-(MOD/i)*inv[MOD%i])%MOD+MOD)%MOD;

扩展欧几里德

可以求解形如 a x + b y = c ax+by=c ax+by=c 的不定方程。

原理: a x + b y = gcd ⁡ ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b) 有解,并且可以递归求解。

首先根据欧几里德定理, gcd ⁡ ( a , b ) = gcd ⁡ ( b , a % b ) = gcd ⁡ ( b , a − b × ⌊ a b ⌋ ) \gcd(a,b)=\gcd(b,a\%b)=\gcd(b,a-b\times\left\lfloor\dfrac{a}{b}\right\rfloor) gcd(a,b)=gcd(b,a%b)=gcd(b,ab×ba)

所以有 a x 1 + b y 1 = b x 2 + ( a − b × ⌊ a b ⌋ ) y 2 = a y 2 + b ( x 2 − ⌊ a b ⌋ y 2 ) ax_1+by_1=bx_2+(a-b\times\left\lfloor\dfrac{a}{b}\right\rfloor)y_2=ay_2+b(x_2-\left\lfloor\dfrac{a}{b}\right\rfloor y_2) ax1+by1=bx2+(ab×ba)y2=ay2+b(x2bay2)

所以我们从上层递归得到 x 2 x_2 x2 y 2 y_2 y2,直接代入得到当前的 x 1 x_1 x1 y 1 y_1 y1

ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){x=1,y=0;return a;}
	int r=exgcd(b,a%b,y,x);
	y-=a/b*x;return r;
}

注意这样求出来的是最小非负整数解,我们还需要知道通解是 x = x 0 + k b gcd ⁡ ( a , b ) x=x_0+\dfrac{kb}{\gcd(a,b)} x=x0+gcd(a,b)kb y = y 0 − k a gcd ⁡ ( a , b ) y=y_0-\dfrac{ka}{\gcd(a,b)} y=y0gcd(a,b)ka。然后就是由于解的方程是 a x + b y = gcd ⁡ ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b),所以我们需要判断 c c c 模上 gcd ⁡ ( a , b ) \gcd(a,b) gcd(a,b) 是否等于零,否则无解。

中国剩余定理

有一说一这东西真的会考么(((
但还是背一下板子吧,万一呢。。。

ll CRT(ll n){
	ll ans=0,M=1,x,y,mi;
	for(int i=1;i<=n;i++) M*=m[i];
	for(int i=1;i<=n;i++){
		mi=M/m[i];
		exgcd(mi,m[i],x,y);
		x=(x%m[i]+m[i])%m[i];
		ans=(ans+r[i]*mi*x)%M;
	}
	return (ans%M+M)%M;
}

组合数学

这东西真的没啥板子吧……

首先基础是会快速求阶乘和阶乘的逆元……

void init(){
	fac[0]=ifac[0]=1;
	for(int i=1;i<=MAXN;i++)
		fac[i]=fac[i-1]*i%MOD;
	ifac[MAXN]=inv(fac[MAXN]);//暴力O(log)求
	for(int i=MAXN-1;i>=1;i--)
		ifac[i]=ifac[i+1]*(i+1)%MOD;
}

然后是卡特兰数

ll Cat(int n){return ((C(2*n,n)-C(2*n,n+1))%MOD+MOD)%MOD;}

线性代数

矩乘

矩阵乘法与快速幂,是基于简单递推式和矩阵乘法的结合率产生的一种加速算法。

给出递推式 f i = f i − 1 + f i − 3 f_i=f_{i-1}+f_{i-3} fi=fi1+fi3

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
const int siz=3;
struct Matrix{ll a[siz][siz];};
Matrix mul(Matrix m1,Matrix m2){
	Matrix ret;
	memset(ret.a,0,sizeof(ret.a));
	for(int i=0;i<siz;i++)
		for(int k=0;k<siz;k++){
			if(!m1.a[i][k]) continue;
			for(int j=0;j<siz;j++)
				ret.a[i][j]=(ret.a[i][j]+m1.a[i][k]*m2.a[k][j]%MOD)%MOD;
		}
	return ret;
}
Matrix qpow(Matrix m,ll p){
	Matrix ret;
	memset(ret.a,0,sizeof(ret.a));
	for(int i=0;i<siz;i++) ret.a[i][i]=1;
	while(p){
		if(p&1) ret=mul(ret,m);
		m=mul(m,m); p>>=1;
	}return ret;
}
void solve(){
	ll n;
	Matrix x,op;
	x.a[0][0]=1;
	x.a[1][0]=1;
	x.a[2][0]=1;
	op.a[0][0]=1;op.a[0][1]=0;op.a[0][2]=1;
	op.a[1][0]=1;op.a[1][1]=0;op.a[1][2]=0;
	op.a[2][0]=0;op.a[2][1]=1;op.a[2][2]=0;
	scanf("%lld",&n);
	if(n==1||n==2||n==3){puts("1");return;}
	printf("%lld\n",mul(qpow(op,n-3),x).a[0][0]%MOD);
}
int main()
{
	int T;
	for(scanf("%d",&T);T--;)
		solve();
}

高斯消元

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值