【Tarjan算法】求连通块+割点/边
1.Tarjan算法的作用
(1)割点的个数以及输出割点序号
(2)连通块的数目
(3)最大连通块的元素个数
2.模板
#include <iostream> #include <algorithm> #include <cstdio> #include <cmath> #include <cstdlib> #include <string> #include <cstring> #include <queue> #include <vector> #include <map> #include <set> #include <stack> #define inf 0x3f3f3f3f #define ll long long #define sscc ios::sync_with_stdio(false); #define ms(a) memset(a,0,sizeof(a)) #define mss(a) memset(a,-1,sizeof(a)) #define msi(a) memset(a,inf,sizeof(a)) using namespace std; const int M=2e5+6; int ans;//ans是最大连通块的数目 int pre[M],ten;//链式前向星 int dfn[M],low[M],cens,vis[M];//tarjan int cut[M]; stack <int> st; int cutnode[M];//存割点的序号 struct A{ int y,next; }tens[M]; int add(int x,int y)//加边 { tens[ten].y=y,tens[ten].next=pre[x],pre[x]=ten++; } int root; void tarjan(int v) { int flag=0; dfn[v]=low[v]=cens++,vis[v]=1,st.push(v);//分配一个序号 ,并进栈,标记 for(int i=pre[v];~i;i=tens[i].next)//遍历v点出发的全部边 { int u=tens[i].y;//v->u的u点 if(dfn[u]==0)//如果这个点没访问过 { tarjan(u);//向下访问 low[v]=min(low[v],low[u]);//去v和u的最小序 if (low[u]>=dfn[v]) //这就是割点的判定 { flag++;//割点数量++ if (v!=root || flag>1)//不能是根节点,或者说是根节点,但是有至少两个子树节点是割点 cut[v]=1; } }else if(vis[u])//如果访问过,那就取最小序 low[v]=min(low[v],dfn[u]); } if(dfn[v]==low[v])//将该连通子图回退 { int nums=0,j; do{ j=st.top();//取栈顶 // cout<<j<<" ";//连通块元素 st.pop();//出栈 vis[j]=0;//取消标记 nums++;//统计该强连通分量的点的数量 }while(j!=v);//直到栈顶是v为止(v会被拿出来) ans=max(ans,nums);//ans是最大连通块的数目 } } int main() { sscc; int n,m; while(cin>>n>>m&&(n+m)) { mss(pre),ten=0,cens=1,ans=-inf; ms(dfn),ms(low),ms(vis),ms(cut); for(int i=0;i<m;i++) { int x,y; cin>>x>>y; add(x,y); add(y,x); } for(int i=1;i<=n;i++)//防止有点不连通 { if(!dfn[i]){ root=i,tarjan(i); } } //统计割点个数 int cutnum=0; for(int i=1; i<=n; i++) if (cut[i]){ cutnode[cutnum]=i; cutnum++; } cout<<cutnum<<endl; sort(cutnode,cutnode+cutnum);//从小到大输出割点序号 for(int i=0; i<cutnum; i++) { if(i!=cutnum-1) cout<<cutnode[i]<<" "; else cout<<cutnode[i]<<endl; } } return 0; }
3.例题
1.hdu 1269----求最大连通块的元素数目
#include <iostream> #include <algorithm> #include <cstdio> #include <cmath> #include <cstdlib> #include <string> #include <cstring> #include <queue> #include <vector> #include <map> #include <set> #include <stack> #define inf 0x3f3f3f3f #define ll long long #define sscc ios::sync_with_stdio(false); #define ms(a) memset(a,0,sizeof(a)) #define mss(a) memset(a,-1,sizeof(a)) #define msi(a) memset(a,inf,sizeof(a)) using namespace std; const int M=2e5+6; int ans;//ans是最大连通块的数目 int pre[M],ten;//链式前向星 int dfn[M],low[M],cens,vis[M];//tarjan int cut[M]; stack <int> st; int cutnode[M];//存割点的序号 struct A{ int y,next; }tens[M]; int add(int x,int y)//加边 { tens[ten].y=y,tens[ten].next=pre[x],pre[x]=ten++; } int root; void tarjan(int v) { int flag=0; dfn[v]=low[v]=cens++,vis[v]=1,st.push(v);//分配一个序号 ,并进栈,标记 for(int i=pre[v];~i;i=tens[i].next)//遍历v点出发的全部边 { int u=tens[i].y;//v->u的u点 if(dfn[u]==0)//如果这个点没访问过 { tarjan(u);//向下访问 low[v]=min(low[v],low[u]);//去v和u的最小序 if (low[u]>=dfn[v]) //这就是割点的判定 { flag++;//割点数量++ if (v!=root || flag>1)//不能是根节点,或者说是根节点,但是有至少两个子树节点是割点 cut[v]=1; } }else if(vis[u])//如果访问过,那就取最小序 low[v]=min(low[v],dfn[u]); } if(dfn[v]==low[v])//将该连通子图回退 { int nums=0,j; do{ j=st.top();//取栈顶 // cout<<j<<" ";//连通块元素 st.pop();//出栈 vis[j]=0;//取消标记 nums++;//统计该强连通分量的点的数量 }while(j!=v);//直到栈顶是v为止(v会被拿出来) ans=max(ans,nums);//ans是最大连通块的数目 } } int main() { sscc; int n,m; while(cin>>n>>m&&(n+m)) { mss(pre),ten=0,cens=1,ans=-inf; ms(dfn),ms(low),ms(vis),ms(cut); for(int i=0;i<m;i++) { int x,y; cin>>x>>y; add(x,y); //add(y,x); } for(int i=1;i<=n;i++)//防止有点不连通 { if(!dfn[i]){ root=i,tarjan(i); } } /*//统计割点个数 int cutnum=0; for(int i=1; i<=n; i++) if (cut[i]){ cutnode[cutnum]=i; cutnum++; } cout<<cutnum<<endl; sort(cutnode,cutnode+cutnum);//从小到大输出割点序号 for(int i=0; i<cutnum; i++) { if(i!=cutnum-1) cout<<cutnode[i]<<" "; else cout<<cutnode[i]<<endl; }*/ if(ans==n)printf("Yes\n"); else printf("No\n"); } return 0; }
2.P3388 【模板】割点(割顶)-----求图的割点数目以及输出各割点的序号
#include <iostream> #include <algorithm> #include <cstdio> #include <cmath> #include <cstdlib> #include <string> #include <cstring> #include <queue> #include <vector> #include <map> #include <set> #include <stack> #define inf 0x3f3f3f3f #define ll long long #define sscc ios::sync_with_stdio(false); #define ms(a) memset(a,0,sizeof(a)) #define mss(a) memset(a,-1,sizeof(a)) #define msi(a) memset(a,inf,sizeof(a)) using namespace std; const int M=2e5+6; int pre[M],ten,ans;//链式前向星 int dfn[M],low[M],cens,vis[M];//tarjan int cut[M]; stack <int> st; int cutnode[M];//存割点的序号 struct A{ int y,next; }tens[M]; int add(int x,int y)//加边 { tens[ten].y=y,tens[ten].next=pre[x],pre[x]=ten++; } int root; void tarjan(int v) { int flag=0; dfn[v]=low[v]=cens++,vis[v]=1,st.push(v);//分配一个序号 ,并进栈,标记 for(int i=pre[v];~i;i=tens[i].next)//遍历v点出发的全部边 { int u=tens[i].y;//v->u的u点 if(dfn[u]==0)//如果这个点没访问过 { tarjan(u);//向下访问 low[v]=min(low[v],low[u]);//去v和u的最小序 if (low[u]>=dfn[v]) //这就是割点的判定 { flag++;//割点数量++ if (v!=root || flag>1)//不能是根节点,或者说是根节点,但是有至少两个子树节点是割点 cut[v]=1; } }else if(vis[u])//如果访问过,那就取最小序 low[v]=min(low[v],dfn[u]); } if(dfn[v]==low[v])//将该连通子图回退 { int nums=0,j; do{ j=st.top();//取栈顶 // cout<<j<<" ";//连通块元素 st.pop();//出栈 vis[j]=0;//取消标记 nums++;//统计该强连通分量的点的数量 }while(j!=v);//直到栈顶是v为止(v会被拿出来) ans=max(ans,nums);//ans是最大连通块的数目 } } int main() { sscc; int n,m; while(cin>>n>>m&&(n+m)) { mss(pre),ten=0,cens=1,ans=-inf; ms(dfn),ms(low),ms(vis),ms(cut); for(int i=0;i<m;i++) { int x,y; cin>>x>>y; add(x,y); add(y,x); } for(int i=1;i<=n;i++)//防止有点不连通 { if(!dfn[i]){ root=i,tarjan(i); } } int cutnum=0; for(int i=1; i<=n; i++) //统计割点个数 if (cut[i]){ cutnode[cutnum]=i; cutnum++; } cout<<cutnum<<endl; sort(cutnode,cutnode+cutnum);//从小到大输出割点序号 for(int i=0; i<cutnum; i++) { if(i!=cutnum-1) cout<<cutnode[i]<<" "; else cout<<cutnode[i]<<endl; } } return 0; }

浙公网安备 33010602011771号