CSP 赛前的模板复习。其中代码一般以洛谷模板题为基础。变量类型一般为 int
,数据范围一般为
1
0
5
10^5
105,算法范围一般为 提高级 。
具体还是看代码。
LIST:
数据结构
倍增表(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)=∏(1−pi1)×piki=n×∏(1−pi1)
于是我们就有:
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)×(p1−1)。
但是还需要考虑的是 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) ap−1≡1(modp) 得到 a p − 2 ≡ 1 a ( m o d p ) a^{p-2}\equiv\dfrac{1}{a}(\mod p) ap−2≡a1(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×inv≡1(modp),展开后可以得到 i n v × a − k × p = 1 inv\times a-k\times p=1 inv×a−k×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) p≡0(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+j≡0(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,a−b×⌊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+(a−b×⌊ba⌋)y2=ay2+b(x2−⌊ba⌋y2)。
所以我们从上层递归得到 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=y0−gcd(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=fi−1+fi−3
#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();
}