模板整理——图论部分
- 图论
- 最短路算法1
- 最短路算法2
- Floyd 算法(全源最短路)(\(\mathcal O(n^3)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 105
int e[MAXN][MAXN];
int n,m;
int u,v,w;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) e[i][j]=1000000000;
for(int i=1;i<=n;i++) e[i][i]=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
e[u][v]=min(e[u][v],w);
}
for(int k=1;k<=n;k++)//注意枚举顺序
{
for(int j=1;j<=n;j++)
{
for(int i=1;i<=n;i++)
{
e[i][j]=min(e[i][j],e[i][k]+e[k][j]);
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++) printf("%d ",e[i][j]);
puts("");
}
return puts(""),0;
}
- Dijkstra (单源最短路)(朴素实现为 \(\mathcal O(n^2)\),经过优化(一般是堆优化)可以做到 \(\mathcal O(n\log n)\),但不可以处理负权边)
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define MAXN 100005
int n,m,s;
int u,v,c;
int dis[MAXN],vis[MAXN];
struct node
{
int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
void add(int u,int v,int w){e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u],head[u]=cnt;}
void dij()
{
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+e[i].w)
{
dis[j]=dis[u]+e[i].w;
q.push(make_pair(dis[j],j));
}
}
}
return;
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++) scanf("%d%d%d",&u,&v,&c),add(u,v,c);
for(int i=1;i<=n;i++) dis[i]=0x7fffffff;
dis[s]=0,q.push(make_pair(0,s));
dij();
for(int i=1;i<=n;i++) printf("%d ",dis[i]);
return puts(""),0;
}
- SPFA(单源最短路)(是对 Bellman-Ford 算法的一种优化,在随机数据下期望复杂度是 \(\mathcal O(kn)\),但容易被卡成 \(\mathcal O(n^2)\),故慎用,但优势是可以处理负权边)
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define MAXN 100005
int n,m,s;
int u,v,c;
int dis[MAXN],vis[MAXN];
struct node
{
int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0;
queue<int>q;
void add(int u,int v,int w){e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u],head[u]=cnt;}
void SPFA()
{
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+e[i].w)
{
dis[j]=dis[u]+e[i].w;
if(!vis[j]) q.push(j),vis[j]=1;
}
}
}
return;
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++) scanf("%d%d%d",&u,&v,&c),add(u,v,c);
for(int i=1;i<=n;i++) dis[i]=0x7fffffff;
dis[s]=0,q.push(s);
SPFA();
for(int i=1;i<=n;i++) printf("%d ",dis[i]);
return puts(""),0;
}
- 最小生成树(MST 问题)
- Kruskal (\(\mathcal O(m\log m)\))
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
int n,m;
int sum,tot;
int fa[5005];
struct node
{
int u,v,w;
}e[200005];
bool cmp(node n,node m){return n.w<m.w;}
void init(int n){for(int i=1;i<=n;i++) fa[i]=i;}
int getf(int u){return fa[u]=(fa[u]==u)?u:getf(fa[u]);}
int merge(int u,int v)
{
int t1=getf(u),t2=getf(v);
if(t1^t2){fa[t2]=t1;return 1;}
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
init(n);
for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++)
{
if(merge(e[i].u,e[i].v))
{
tot++,sum+=e[i].w;
if(tot==n-1) break;
}
}
if(tot<n-1) puts("orz");
else printf("%d\n",sum);
return 0;
}
- Prim(使用堆优化 \(\mathcal O(n\log n)\))
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
using namespace std;
#define MAXN 200005
int n,m;
int sum,tot;
int u,v,w;
struct node
{
int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int dis[MAXN],vis[MAXN];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
void add(int u,int v,int w){e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u],head[u]=cnt;}
int Prim()
{
while(!q.empty()&&tot!=n)
{
int u=q.top().second,c=q.top().first;
q.pop();
if(vis[u]) continue;
vis[u]=1;
tot++,sum+=c;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>e[i].w) dis[j]=e[i].w,q.push(make_pair(dis[j],j));
}
}
if(tot==n) return 1;//注意这里指的是n个点而非边
else 0;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
for(int i=1;i<=n;i++) dis[i]=0x7fffffff;
dis[1]=0,q.push(make_pair(0,1));
int flag=Prim();
if(!flag) puts("orz");
else printf("%d\n",sum);
return 0;
}
- 负环判断(\(\mathcal O(kn)\))
利用玄学的已死的 没死光的 SPFA 进行判断。
#include"iostream"
#include"cstring"
#include"cstdio"
#include"queue"
#include"cmath"
using namespace std;
#define MAXN 2005
#define inf 0x7fffffff
#define read(x) scanf("%d",&x)
#define mem(s) memset(s,0,sizeof(s))
int n,m;
int u,v,w;
int t,dis[MAXN],len[MAXN];
int vis[MAXN];
queue<int> q;
struct node
{
int to,nxt,w;
}e[MAXN<<2];
int head[MAXN],cnt=0;
void add(int u,int v,int w){e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u];head[u]=cnt;}
int SPFA()
{
while(!q.empty())
{
int u=q.front();
q.pop(),vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+e[i].w)
{
dis[j]=dis[u]+e[i].w,len[j]=len[u]+1;
if(len[j]>n) return 1;
if(!vis[j]) vis[j]=1,q.push(j);
}
}
}
return 0;
}
int main()
{
read(t);
while(t--)
{
read(n),read(m);
mem(vis),mem(len),mem(head),mem(e);
while(!q.empty()) q.pop();
cnt=0;
for(int i=1;i<=m;i++)
{
read(u),read(v),read(w);
if(w>=0) add(v,u,w);
add(u,v,w);
}
for(int i=1;i<=n;i++) dis[i]=inf;
dis[1]=0,vis[1]=1,q.push(1);
(SPFA())?puts("YES"):puts("NO");
}
return 0;
}
- 差分约束系统(\(\mathcal O(kn)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 5005
#define read(x) scanf("%d",&x)
int n,m;
int x,y,z;
struct node
{
int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int dis[MAXN],vis[MAXN];
int cou[MAXN]={0},que[MAXN*MAXN],he=1,ta=1;
void add(int u,int v,int w)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].w=w;
head[u]=cnt;
}
void SPFA()
{
que[1]=0,vis[0]=1,dis[0]=0;
while(he>=ta)
{
int u=que[ta++];
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+e[i].w)
{
dis[j]=dis[u]+e[i].w,cou[j]=cou[u]+1;
if(!vis[j]) que[++he]=j,vis[j]=1;
if(cou[j]>=n+2){puts("NO");return;}
}
}
}
for(int i=1;i<=n;i++) printf("%d ",dis[i]);
puts("");
return;
}
int main()
{
read(n),read(m);
for(int i=1;i<=m;i++) read(x),read(y),read(z),add(y,x,z);
for(int i=1;i<=n;i++) add(0,i,0);
for(int i=1;i<=n;i++) dis[i]=1e9;
SPFA();
return 0;
}
- 树的直径
- 两次 dfs(\(\mathcal O(n)\))
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define MAXN 10005
#define read(x) scanf("%d",&x)
int n,u,v;
int len=0,pos;
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void dfs(int cur,int fa,int stp)
{
if(stp>len) len=stp,pos=cur;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
dfs(j,cur,stp+1);
}
}
int main()
{
read(n);
for(int i=1;i<n;i++) read(u),read(v),add(u,v),add(v,u);
dfs(1,0,0),len=0,dfs(pos,0,0);
printf("%d\n",len);
return 0;
}
- 树形 dp(\(\mathcal O(n)\))
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define MAXN 10005
#define read(x) scanf("%d",&x)
int n,u,v;
int dp[MAXN],ans=0;
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void dfs(int cur,int fa)
{
int maxn=0,maxnn=0;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
dfs(j,cur);
if(maxn<=dp[j]+1) maxnn=maxn,maxn=dp[j]+1;
else if(maxnn<dp[j]+1) maxnn=dp[j]+1;
}
dp[cur]=maxn,ans=max(ans,maxn+maxnn);
}
int main()
{
read(n);
for(int i=1;i<n;i++) read(u),read(v),add(u,v),add(v,u);
dfs(1,0);
printf("%d\n",ans);
return 0;
}
- 树的重心(\(\mathcal O(n)\))
这题模板题是一道私题,我就不放了,这里只放代码。
#include"iostream"
#include"cstdio"
#include"cstring"
#include"algorithm"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 500005
int n;
int d[MAXN],num,minx=0x7fffffff;
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int x,y,c=0,cen[15];
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
int dfs(int cur,int fa)
{
d[cur]=1;
int maxn=0;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
int now=dfs(j,cur);
maxn=max(maxn,now);
d[cur]+=now;
}
maxn=max(maxn,n-d[cur]);
if(maxn<minx) minx=maxn,num=cur,c=1,cen[c]=cur;
else if(maxn==minx) c++,cen[c]=cur;
return d[cur];
}
int main()
{
read(n);
for(int i=1;i<n;i++) read(x),read(y),add(x,y),add(y,x);
dfs(1,0);
sort(cen+1,cen+c+1);
for(int i=1;i<=c;i++) printf("%d ",cen[i]);
return puts(""),0;
}
- 最近公共祖先(LCA)
- 倍增(\(\mathcal O(n\log n)\))
这是传统写法,然而我不会。
- tarjan(离线做法)(\(\mathcal O(n\alpha(n)+q)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 500005
int n,root,qe;
int u,v;
int f[MAXN],vis[MAXN];
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
struct query
{
int to,nxt,id;
}q[MAXN<<1];
int sta[MAXN],cntt=0;
int ans[MAXN];
void add1(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void add2(int u,int v,int rt){q[++cntt].to=v,q[cntt].nxt=sta[u],q[cntt].id=rt,sta[u]=cntt;}
void init(int n){for(int i=1;i<=n;i++) f[i]=i,vis[i]=0;}
int getf(int u){return f[u]=(f[u]==u)?u:getf(f[u]);}
void merge(int u,int v){int t1=getf(u),t2=getf(v);if(t1^t2) f[t2]=t1;}
void dfs(int cur,int fa)
{
vis[cur]=1;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
dfs(j,cur);
merge(cur,j);//千千万万别写反了
}
for(int i=sta[cur];i;i=q[i].nxt)
{
int j=q[i].to;
if(vis[j]) ans[q[i].id]=getf(j);
}
return;
}
int main()
{
scanf("%d%d%d",&n,&qe,&root);
init(n);
for(int i=1;i<n;i++) scanf("%d%d",&u,&v),add1(u,v),add1(v,u);
for(int i=1;i<=qe;i++) scanf("%d%d",&u,&v),add2(u,v,i),add2(v,u,i);
dfs(root,root);
for(int i=1;i<=qe;i++) printf("%d\n",ans[i]);
return 0;
}
- 树链剖分(\(\mathcal O(n\log n)\))
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 500005
int top[MAXN],dep[MAXN],son[MAXN],vis[MAXN],f[MAXN],tot[MAXN];
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int n,m,root,l,r;
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
int dfsa(int cur,int deep)
{
vis[cur]=1;
dep[cur]=deep;
tot[cur]=1;
son[cur]=0;
int maxn=0;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(vis[j]) continue;
f[j]=cur;
int now=dfsa(j,deep+1);
if(now>maxn) maxn=now,son[cur]=j;
tot[cur]+=now;
}
return tot[cur];
}
void dfsb(int cur,int topf)
{
vis[cur]=1;
top[cur]=topf;
if(son[cur]) dfsb(son[cur],topf);
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(vis[j]) continue;
dfsb(j,j);
}
return;
}
int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=f[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int main()
{
read(n),read(m),read(root);
for(int i=1;i<n;i++)
{
read(l),read(r);
add(l,r),add(r,l);
}
memset(vis,0,sizeof(vis));
f[root]=root;
dfsa(root,1);
memset(vis,0,sizeof(vis));
dfsb(root,root);
for(int i=1;i<=m;i++)
{
read(l),read(r);
printf("%d\n",lca(l,r));
}
return 0;
}
- ST 表(\(\mathcal O(n\log n)\))
学那么多干啥,有两种方法就够了/kk。
- 缩点(tarjan)(\(\mathcal O(n+m)\))
缩的就是你,强联通分量!
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 10005
#define mem(s) memset(s,0,sizeof(s))
int n,m;
int head[MAXN],cnt=0;
int cou=0,a[MAXN],id=0;
struct node
{
int to,nxt;
}e[MAXN*10];
int be[MAXN],si[MAXN],val[MAXN],vis[MAXN];
int low[MAXN],num[MAXN];
int x[MAXN*10],y[MAXN*10];
int s[MAXN],top=0;
int q[MAXN],rt=0,ta=1,in[MAXN];
int dp[MAXN],ans=0;
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs(int cur)
{
low[cur]=num[cur]=++id;
vis[cur]=1,s[++top]=cur;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(!vis[j]) dfs(j);
if(!be[j]) low[cur]=min(low[cur],low[j]);
}
if(low[cur]==num[cur])
{
cou++;
while(1)
{
int j=s[top--];
be[j]=cou,val[cou]+=a[j],si[cou]++;
if(j==cur) break;
}
}
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=m;i++)
{
read(x[i]),read(y[i]);
add(x[i],y[i]);
}
for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);
cnt=0,mem(head),mem(e);
for(int i=1;i<=m;i++) if(be[x[i]]!=be[y[i]]) add(be[x[i]],be[y[i]]),in[be[y[i]]]++;
for(int i=1;i<=cou;i++) if(!in[i]) q[++rt]=i,dp[i]=val[i],ans=max(ans,dp[i]);
while(ta<=rt)
{
int u=q[ta];
ta++;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
in[j]--;
dp[j]=max(dp[j],dp[u]+val[j]),ans=max(ans,dp[j]);
if(!in[j]) q[++rt]=j;
}
}
printf("%d\n",ans);
return 0;
}
- 割点(tarjan)(\(\mathcal O(n+m)\))
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 20005
#define read(x) scanf("%d",&x)
int n,m;
int low[MAXN],num[MAXN],vis[MAXN];
int head[MAXN],cnt=0;
int c=0;
struct node
{
int to,nxt;
}e[MAXN*10];
int u,v;
int id=0;
int child=0;
int ma[MAXN];
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void dfs(int cur,int fa)
{
num[cur]=low[cur]=++id;
vis[cur]=1;
int child=0;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(!vis[j])
{
child++;
dfs(j,cur);
low[cur]=min(low[cur],low[j]);
if(fa!=cur&&low[j]>=num[cur]&&!ma[cur]) c++,ma[cur]=1;
}
else if(j!=fa) low[cur]=min(low[cur],num[j]);
}
if(cur==fa&&child>=2&&!ma[cur]) c++,ma[cur]=1;
return;
}
int main()
{
read(n),read(m);
for(int i=1;i<=m;i++) read(u),read(v),add(u,v),add(v,u);
for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,i);
printf("%d\n",c);
for(int i=1;i<=n;i++) if(ma[i]) printf("%d ",i);
return puts(""),0;
}
无向图缩点用 tarjan 割点类似的方法实现。
割边(桥)的判断方法是 low[j]>num[cur]
,但不需要特判根节点。
- 二分图匹配
- 匈牙利算法(\(\mathcal O(ne)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 505
#define read(x) scanf("%d",&x)
int n,m,k;
int u,v;
struct node
{
int to,nxt;
}e[50005];
int head[MAXN],cnt=0;
int mark[MAXN],vis[MAXN];
int ans=0;
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
bool dfs(int cur)
{
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to-n;
if(vis[j]) continue;
vis[j]=1;
if(!mark[j]||dfs(mark[j])){mark[j]=cur;return true;}
}
return false;
}
int main()
{
read(n),read(m),read(k);
for(int i=1;i<=k;i++) read(u),read(v),add(u,n+v);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) vis[j]=0;
if(dfs(i)) ans++;
}
return printf("%d\n",ans),0;
}
- Dinic(\(\mathcal O(n\sqrt{e})\))
看什么看我又不会网络流。