浅谈强联通分量

埋个强连通分量教程的坑:

 

其实现在可以看一下kosaraju的思路,每次dfs我们可以得到一棵dfs树,如果我们从明智的节点选择开始dfs可以方便的通过反向有向边得到一个强联通分量,但不明智的节点选择则会直接遍历整个图,通过上面个的例图可以看到拓扑序末端的的节点能恰好遍历一个联通分量,而一棵dfs树其实就是一个图的遍历顺序,并且最末端的就是拓扑序末端,由此我们先dfs得到一棵dfs树,然后从拓扑序末端开始再次dfs,同时不断删除(标记)节点,在找出一个联通分量后,实际上dfs树中最后的未被标记的节点又是拓扑序末端节点,由此,两遍dfs即可完成算法。

而tarjan是当dfs树出现返祖边时直接分离联通分量,所以更快。

虽然是第一个我做强连通的题,不过没什么特别的,就1A了

题目

这个题是一个很简单的强联通压点的题目,Tarjan和kosaraju都可以做,首先用联通的算法求T1,即大小大于1的强连通分量数目,再压一下点,T2实际上是求出度为零的强连通量。这里可以证明一下,如果有多个满足题意,但压点后的图是一个有向无环图,则必定不可能出现,若少于一个,则图不联通,就更不可能达到题意,所以反证一下很容易就理解了题意,当然,这里的压点可以不是很严格,不用再开一个vector临接表,只需用bool数组判断一下出度,再输出结果就好了。

代码是用kosaraju写的,有点长,而且全局变量太多了,感觉写的很丑。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn = 10000;

struct Edge
{
    int from,to,dist;
};
vector<int> G1[maxn];
vector<int> G2[maxn];
int vis[maxn];
int sccno[maxn];
int scc_cnt;
vector<int> S;
vector<int> group[maxn];
void dfs1(int u)
{
    if(vis[u]) return;
    vis[u] = 1;
    for(int i = 0;i < G1[u].size();i++) dfs1(G1[u][i]);
    S.push_back(u); 
}

void dfs2(int u)
{
    if(sccno[u]) return;
    sccno[u] = scc_cnt;
    group[scc_cnt].push_back(u);
    for(int i = 0;i < G2[u].size() ;i++) dfs2(G2[u][i]);
}

void read(int& N,int& M)
{
    scanf("%d%d",&N,&M);
    for(int i = 0;i < M;i++)
    {
        int from,to;
        scanf("%d%d",&from,&to);
        G1[from].push_back(to);
        G2[to].push_back(from);
    }
}
int main()
{
    scc_cnt = 0;
    S.clear();
    memset(vis,0,sizeof(vis));
    memset(sccno,0,sizeof(sccno));
    int N,M;
    read(N,M);
    for(int i = 1;i <= N;i++) dfs1(i);
    for(int i = N-1;i >= 0;i--)
    {
        if(!sccno[S[i]]){
            scc_cnt++;
            dfs2(S[i]);            
        }
    }
    int ans  = 0;
    for(int i = 1;i <= scc_cnt;i++)
    {
        if(group[i].size() > 1) ans++;
    }
    cout<<ans<<endl;
    int out[maxn];
    for(int i = 1;i <= scc_cnt;i++) out[i] = 0;
    for(int i = 1;i <= scc_cnt;i++)
    {
        for(int j = 0;j < group[i].size();j++)
        {
            int v = group[i][j];
            for(int k = 0;k < G1[v].size();k++)
            {
                int x = G1[v][k];
                    if(sccno[v]!= sccno[x])
                    out[sccno[v]] = 1;
    
            }

        }
    }
    int angle = 0;
    for(int i = 1;i <= scc_cnt;i++ )
    {
        if(angle && out[i] == 0)
        {
            cout<<"-1"<<endl;
            return 0;
        }
        if(out[i] == 0&&group[i].size() > 1) angle = i;
    }
    if(angle == 0) cout<<"-1"<<endl;
    sort(group[angle].begin(),group[angle].end());
    for(int i = 0;i <group[angle].size();i++)
    {
        printf("%d ",group[angle][i]);
    }
    cout<<endl;
    return 0;
}
kosaraju

当然,用tarjan也是一样的:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn = 10000;

struct Edge
{
    int from,to,dist;
};

vector<int> G[maxn];
int vis[maxn],pre[maxn],sccno[maxn];
int dfs_clock,scc_cnt;
stack<int> S;
vector<int> group[maxn];
int lowlink[maxn];

void read(int& N,int& M)
{
    scanf("%d%d",&N,&M);
    for(int i = 0;i < M;i++)
    {
        int from,to;
        scanf("%d%d",&from,&to);
        G[from].push_back(to);
    }
}

void dfs(int u)
{
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for(int i = 0;i < G[u].size();i++)
    {
        int v = G[u][i];
        if(!pre[v]){
            dfs(v);
            lowlink[u] = min(lowlink[u],lowlink[v]);
        }else if(!sccno[v]){
            lowlink[u] = min(lowlink[u],pre[v]);
        }
    }
    if(lowlink[u] == pre[u]){
        scc_cnt++;
        for(;;){
            int x = S.top();S.pop();
            sccno[x] = scc_cnt;
            group[scc_cnt].push_back(x);
            if(x == u) break;
        }
    }
}


void tarjan(int N)
{
    scc_cnt = 0;
    dfs_clock = 0;
    memset(vis,0,sizeof(vis));
    memset(sccno,0,sizeof(sccno));
    memset(pre,0,sizeof(pre));
    for(int i = 1;i <= N;i++)
    {
        if(!pre[i]) dfs(i);
    }
    
        
}

int main()
{
    int N,M;
    read(N,M);
    tarjan(N);
    int ans  = 0;
    for(int i = 1;i <= scc_cnt;i++)
    {
        if(group[i].size() > 1) ans++;
    }
    cout<<ans<<endl;
    int out[maxn];
    for(int i = 1;i <= scc_cnt;i++) out[i] = 0;
    for(int i = 1;i <= scc_cnt;i++)
    {
        for(int j = 0;j < group[i].size();j++)
        {
            int v = group[i][j];
            for(int k = 0;k < G[v].size();k++)
            {
                int x = G[v][k];
                    if(sccno[v]!= sccno[x])
                    out[sccno[v]] = 1;
    
            }

        }
    }
    int angle = 0;
    for(int i = 1;i <= scc_cnt;i++ )
    {
        if(angle && out[i] == 0)
        {
            cout<<"-1"<<endl;
            return 0;
        }
        if(out[i] == 0&&group[i].size() > 1) angle = i;
    }
    if(angle == 0) cout<<"-1"<<endl;
    sort(group[angle].begin(),group[angle].end());
    for(int i = 0;i <group[angle].size();i++)
    {
        printf("%d ",group[angle][i]);
    }
    cout<<endl;
    return 0;
}
View Code

并且可以明显的从oj的运算时间上看出tarjan的常数比kosaraju小很多。

2 等价性证明 UVA12167

先用tarjan压下点,然后枚举标记一下DAG中入度为0和出度为0的,取一下max就好了

证明,要使一个DAG强联通,首先,DAG中有有许多出度为0的点和入度为0的点,我们要使图强联通,不妨设出度为0的为n1,入度为0的n2;n1>n2;首先,我们从各个出度为0的点连n2条边指向入度为0的点,这样之后,不存在入度为0的点,出度为0的点减少了n2,然后用tarjan的思路,连返祖边就可以直接使图强联通,这样问题就减少到此时割顶的数量恰好为n1-n2个。此时出度为0的节点数为n1-n2个,这时选一个出度不为0的点dfs,所有出度为0的点都是叶子节点,直接连树根,形成返祖边,图就强联通了,同理可证n1<n2的情况。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<vector>
 5 #include<cstring>
 6 #include<stack>
 7 #include<algorithm>
 8 
 9 using namespace std;
10 const int maxn = 100000;
11 vector<int> G[maxn];
12 stack<int> S;
13 int pre[maxn];int lowlink[maxn];int dfs_clock;
14 int sccno[maxn];int scc_cnt;
15 int in0[maxn],out0[maxn];
16 void dfs(int u)
17 {
18     pre[u] = lowlink[u] = ++dfs_clock;
19     S.push(u);
20     for(int i = 0;i < G[u].size();i++)
21     {
22         int v = G[u][i];
23         if(!pre[v]){
24             dfs(v);
25             lowlink[u] = min(lowlink[u],lowlink[v]);
26         }else if(!sccno[v]){
27             lowlink[u] = min(lowlink[u],pre[v]);
28         }
29     
30     }
31     if(lowlink[u] == pre[u]){
32         scc_cnt++;
33         for(;;)
34         {
35             int x = S.top();S.pop();
36             sccno[x] = scc_cnt;
37             if(x == u) break;
38         }    
39     }
40 }
41 
42 void tarjan(int n)
43 {
44     dfs_clock = scc_cnt = 0;
45     memset(pre,0,sizeof(pre));
46     memset(sccno,0,sizeof(sccno));
47     memset(lowlink,0,sizeof(lowlink));
48     for(int i = 1;i <= n;i++)
49     {
50         if(!pre[i]) dfs(i);
51     }
52 }
53 
54 
55 void Addedge(int from,int to)
56 {
57     G[from].push_back(to);
58 }
59 
60 int main()
61 {
62     int T,a,b;
63     scanf("%d",&T);
64     while(T--)
65     {
66 
67         int n,m;
68         scanf("%d%d",&n,&m);
69         for(int i = 1;i <= n;i++) G[i].clear();
70         for(int i = 0;i < m;i++)
71         {
72             int from,to;
73             scanf("%d%d",&from,&to);
74             Addedge(from,to);        
75         }    
76         tarjan(n);
77         for(int i = 1;i <= scc_cnt;i++) in0[i] = out0[i] = 1;
78         for(int u = 1;u <= n;u++)
79         {
80             for(int i = 0;i <G[u].size();i++)
81             {
82                 int v = G[u][i];
83                 if(sccno[u] != sccno[v]) in0[sccno[v]] = out0[sccno[u]] = 0;
84             }
85         }
86         int a = 0,b = 0;
87         for(int i = 1;i <= scc_cnt;i++)
88         {
89             if(in0[i]) a++;
90             if(out0[i]) b++;
91         }
92         int ans = max(a,b);
93         if(scc_cnt == 1) ans = 0;
94         cout<<ans<<endl;
95     }    
96     return 0;
97 }
等价性证明

 

posted @ 2017-02-19 13:21  rsqppp  阅读(264)  评论(0)    收藏  举报