Tarjan 做题总结
这两天Tarjan复习完后把题做了做。洛谷题单《图的连通性》已经做得差不多了。大部分是Tarjan的题,所以写一篇小总结。
T1 【模板】 缩点
不多bb。我已经写过关于Tarjan模板的随笔了。传送门
T2 【模板】割点
不多bb。传送门
T3 [USACO03FALL][HAOI2006]受欢迎的牛 G
题意简述:找到出度为0的点/强连通分量,并输出大小。
-----------------------------------------------------------------------------------
先缩点。值得注意的是图中只能有一个出度为0的点。如果有更多的出度为0的点那这些出度为0的点无法互相到达,那么数量就会为0。
数量在弹栈的时候记录就好。
代码:
#include<bits/stdc++.h> using namespace std; int head[250005],shabi; int st[250005],top; int vis[250005],dfn[250005],low[250005],all[250005],color[250005],du[250005]; int n,m,cnt,tot,x,y,k; struct node { int next,to; }edge[550005]; void add(int from,int to) { edge[++shabi].next=head[from]; edge[shabi].to=to; head[from]=shabi; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){ if (ch=='-') f=-1; ch=getchar(); }while('0'<=ch&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x*f; } void tarjan(int now) { low[now]=dfn[now]=++cnt; vis[now]=1; st[++top]=now; for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (!dfn[to]) tarjan(to),low[now]=min(low[now],low[to]); else if (vis[to]) low[now]=min(low[now],dfn[to]); } if (dfn[now]==low[now]){ tot++; do{ k=st[top];top--; color[k]=tot; vis[k]=0; all[tot]++; }while(k!=now); } } int main() { n=read(),m=read(); for (int i=1;i<=m;i++) { x=read(),y=read(); add(x,y); } for (int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for (int w=1;w<=n;w++) for (int i=head[w];i;i=edge[i].next) { int to=edge[i].to; if (color[w]!=color[to]) du[color[w]]++; } int flag=0; for (int i=1;i<=tot;i++) { if (!du[i]){ if (flag){ cout<<0; return 0; } flag=i; } } cout<<all[flag]; return 0; }
Tarjan裸题。只要把强连通分量的大小记录下来,如果$num[i]\geq 2$那么$ans++$。输出$ans$即可。
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=10005; const int maxm=50005; int dfn[maxn],low[maxn],vis[maxn],num[maxn],pos[maxn],cnt,tot; int st[maxn],top; int head[maxm],jishu; int n,m,ans; struct node { int next,to; }edge[maxm]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void add(int from,int to) { edge[++jishu].next=head[from]; edge[jishu].to=to; head[from]=jishu; } void tarjan(int now) { vis[now]=1; st[++top]=now; dfn[now]=low[now]=++cnt; for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (!dfn[to]) tarjan(to),low[now]=min(low[now],low[to]); else if (vis[to]) low[now]=min(low[now],dfn[to]); } if (dfn[now]==low[now]) { tot++; while(st[top+1]!=now) { pos[st[top]]=tot; num[tot]++; vis[st[top--]]=0; } } } int main() { n=read(),m=read(); for (int i=1;i<=m;i++) { int u=read(),v=read(); add(u,v); } for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i); for (int i=1;i<=tot;i++) if (num[i]>1) ans++; printf("%d",ans); return 0; }
T5 [USACO5.3]校园网Network of Schools
子问题1就是求缩点后入度为零的点的数量。子问题2就是求$max(ans1,ans2)$。
仔细读读题还是能明白要干什么的。
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=10005; const int maxm=50005; int dfn[maxn],low[maxn],vis[maxn],pos[maxn],cnt,tot; int st[maxn],top; int head[maxm],jishu; int n,fat[maxn][2],k,in[maxn],out[maxn]; struct node { int next,to; }edge[maxm]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void add(int from,int to) { edge[++jishu].next=head[from]; edge[jishu].to=to; head[from]=jishu; } void tarjan(int now) { vis[now]=1;st[++top]=now; dfn[now]=low[now]=++cnt; for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (!dfn[to]) tarjan(to),low[now]=min(low[now],low[to]); else if (vis[to]) low[now]=min(low[now],dfn[to]); } if (dfn[now]==low[now]) { tot++; while(st[top+1]!=now) { pos[st[top]]=tot; vis[st[top--]]=0; } } } int main() { n=read(); for (int i=1;i<=n;i++) { int x=read(); while(x!=0) { add(i,x); fat[++k][0]=i; fat[k][1]=x; x=read(); } } for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i); for (int i=1;i<=k;i++) if (pos[fat[i][0]]!=pos[fat[i][1]]) in[pos[fat[i][1]]]++,out[pos[fat[i][0]]]++; int ans1=0,ans2=0; for (int i=1;i<=tot;i++) { if (in[i]==0) ans1++; if (out[i]==0) ans2++; } if (tot==1) cout<<1<<endl<<0; else cout<<ans1<<endl<<max(ans1,ans2); return 0; }
T6 上白泽慧音
在弹栈的时候记录强连通分量的大小和字典序就行了。
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+7; const int inf=0x3f3f3f3f; struct note{ int to,nex; }e[maxn*3]; int col,num,head[maxn],dfn[maxn],low[maxn],de[maxn],co[maxn],si[maxn],stk[maxn]; int top,n,m; int cnt=-1; void add(int x,int y) { static int cnt=0; cnt++; e[cnt].to=y; e[cnt].nex=head[x]; head[x]=cnt; } void tarjan(int u) { dfn[u]=low[u]=++num; stk[++top]=u; for(int i=head[u];i;i=e[i].nex) { int v=e[i].to; if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(!co[v])low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { co[u]=++col; ++si[col]; while(stk[top]!=u) { ++si[col]; co[stk[top]]=col; --top; } --top; } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int x,y,f; cin>>x>>y>>f; if(f==1)add(x,y); if(f==2)add(x,y),add(y,x); } for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i); for(int i=1;i<=col;i++)cnt=max(cnt,si[i]); cout<<cnt<<endl; for(int i=1;i<=n;i++) { if(si[co[i]]==cnt) { int now=co[i]; for(int j=i;j<=n;j++)if(co[j]==now)cout<<j<<" "; return 0; } } return 0; }
后记:裸的Tarjan题还是很好看出解法的,但是和其他图论算法结合起来就十分难受了,有时候真的是绞尽脑汁也想不出来。