缩点+割点(tarjan)
1.tarjan缩点
思路:找到图中的强联通分量,染成同色,形成DAG(有向无环图).
vis:记录点是否在栈中.
dfn:点在dfs中顺序,即时间戳.
col:同一强连通分量的染色颜色.
low:点与点的子辈能回到的最小时间戳.
其实也可以有其他辅助数组,但核心的数组就这四个.
难点:1.low[x]=min(low[x],low[to]),其实就是不断用子辈low更新,定义可知.
2.dfn[x]=low[x]进行染色操作,用子辈更新low完后回溯发现dfn[x]=low[x],x为强连通分量的根.
以lg3387缩点为例
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define R register #define e exit(0) const int maxn=1e5+10; int n,m,vd=-1,cnt,sum,top,deep,vis[maxn],val[maxn],head[maxn],dfn[maxn],ans[maxn]; int cnt1,head1[maxn],low[maxn],stact[maxn],col[maxn],check[1010][1010],cd[maxn]; struct bian{ int to,next; }len[maxn]; struct bian1{ int to,next; }len1[maxn]; inline int fd() { int s=1,t=0; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') s=-1; c=getchar(); } while(c>='0'&&c<='9') { t=t*10+c-'0'; c=getchar(); } return s*t; } void add(int from,int to) { len[++cnt].to=to; len[cnt].next=head[from]; head[from]=cnt; } void add1(int from,int to) { len1[++cnt1].to=to; len1[cnt1].next=head1[from]; head1[from]=cnt1; } void tarjan(int x) { dfn[x]=++deep; low[x]=deep; stact[++top]=x; vis[x]=1; for(R int k=head[x];k;k=len[k].next) { int to=len[k].to; if(!dfn[to]) { tarjan(to); low[x]=min(low[x],low[to]); } else if(vis[to]) low[x]=min(low[x],low[to]); } if(dfn[x]==low[x]) { int va=0; col[x]=++sum; vis[x]=0; while(stact[top]!=x) { va+=val[stact[top]]; col[stact[top]]=sum; vis[stact[top--]]=0; } va+=val[stact[top]]; ans[sum]=va; --top; } } void dfs(int x,int sumv) { vd=max(sumv,vd); for(R int k=head1[x];k;k=len1[k].next) { int to=len1[k].to; int nowsum=sumv+ans[to]; dfs(to,nowsum); } } int main() { //freopen("s.in","r",stdin); //freopen("s.out","w",stdout); n=fd(),m=fd(); memset(check,0,sizeof(check)); for(R int i=1;i<=n;++i) val[i]=fd(); for(R int i=1;i<=m;++i) { int from=fd(),to=fd(); add(from,to); } for(R int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); if(sum==1) { printf("%d",ans[1]); return 0; } for(R int i=1;i<=n;++i) for(R int k=head[i];k;k=len[k].next) { int to=len[k].to; if(col[i]!=col[to]) { int num1=col[i],num2=col[to]; if(!check[num1][num2]) { check[num1][num2]=1; add1(num1,num2); cd[num1]=1; } } } for(R int i=1;i<=n;++i) if(cd[i]) dfs(i,ans[i]); printf("%d",vd); return 0; }
2.tarjan求割点.
为割点:1.发现子结点并不能通过其他路径到达比祖先深度更浅的节点,祖先为割点.
2.为为枚举的根节点,并且发现其有两个即以上的儿子,这个根节点为割点.
low跟新:low[x]=min(low[x],low[to]),与上面一致.
第二处:if(to!=fa&&dfn[to]<dfn[x]) low[x]=min(low[x],dfn[to]),在各大博客未找到证明.
以lg3388为例:
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; #define R register const int maxn=1e5+10; int n,m,cnt,ans,deep,lenth,head[maxn],dfn[maxn],low[maxn],cut[maxn],q[maxn]; struct bian{ int to,next; }len[maxn<<1]; inline int fd() { int s=1,t=0; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') s=-1; c=getchar(); } while(c>='0'&&c<='9') { t=t*10+c-'0'; c=getchar(); } return s*t; } void add(int from,int to) { len[++cnt].to=to; len[cnt].next=head[from]; head[from]=cnt; } int tarjan(int x,int fa) { int child=0,lowx; lowx=dfn[x]=++deep; for(R int k=head[x];k;k=len[k].next) { int to=len[k].to; if(!dfn[to]) { ++child; int lowto=tarjan(to,x); lowx=min(lowx,lowto); if(lowto>=dfn[x]) cut[x]=1; } else if(to!=fa&&dfn[x]>dfn[to]) lowx=min(lowx,dfn[to]);//low[to]可能走到另一个环,到达更浅的祖先. //此时lowto<dfn[x]而忽略x这个可能的割点. } if(fa<0&&child==1) cut[x]=0; low[x]=lowx; return lowx; } int main() { //freopen("s.in","r",stdin); //freopen("s.out","w",stdout); n=fd(),m=fd(); for(R int i=1;i<=m;++i) { int from=fd(),to=fd(); add(from,to),add(to,from); } for(R int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,-1); for(R int i=1;i<=n;++i) if(cut[i]) { ++ans; q[++lenth]=i; } printf("%d\n",ans); sort(q+1,q+1+lenth); for(R int i=1;i<=lenth;++i) printf("%d ",q[i]); return 0; }
附上dalao博客