【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;
}
View Code

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;
}
View Code

 

posted @ 2019-10-23 16:03  saaas  阅读(282)  评论(0)    收藏  举报