noip2015 信息传递 强连通块

    时隔好久,重新把那些题找出来,写一写了,毕竟实力还很有限,得学。

    一开始,最大的疑惑还是怕一个环里有另外一个环(毕竟经验不丰富),后来看到了这段话

   T2 信息传递

   大意:在一个只有n条有向边的图中,每个结点出度为1,求一个包含节点数最少的环。

   分析:因为只有n条边并且每个点都有且仅有一条边连出去,所以只可能存在简单环,不会出现那种8字形的环套环。

   证明:每个点有两种情况:1.不在任何一个环中 这样的点可以直接忽略掉,看成是有n-1个点,n-1条边来证明就行了。2.在某个环中。那么如果我们现在有了一个圈圈形状的环,大小为k,那么每一个点都应该入度出度都为1,这样的环只存在一个回路,也就是一个简单环,如果存在多个回路,那么应该存在某个点的入度>1,但是又要保证别的点出度至少为1,所以出度之和应该是>k的,但入度之和=k,无法满足,所以不存在某个环存在两条或以上数量的回路。证毕。

   满足了这样的性质,tarjan或者O(n)扫描都是可以解决本题的。

   恍然大悟,既然弱,就多积累吧!

   

   不过,也据说“这题我觉得,真的,画图是关键(我记得我以前的竞赛老师王老师经常说的一件事情就是数形结合),多画图,多模拟,就很容易找到需要解决问题的方法。”。觉得也挺有道理的,发现自己很多时候都是引用别人现成的,属于自己的本质的想法会比较少。

   另附,网上好像还有其他的做法,比如并查集法,还有一种把入度为0的点删去后,再dfs查找的。先给出后者的代码。

即用类似拓扑的方式把所有入度为0的点踢掉,然后可以保证剩下的每个点都在一个有向环中,同时这个图特殊的建法好像可以保证任意两个环之间没有公共点...用on的时间扫一遍就可以了

 1 #include<cstdio>
 2 #include<iostream>
 3 #define rep(i,j,k) for(int i = j; i <= k; i++)
 4 #define maxn 200009
 5 using namespace std;
 6 int next[maxn] = {0};
 7 bool used[maxn] = {0};
 8 int num[maxn] = {0}, ans = 0xfffffff;
 9 
10 int read()
11 {
12     int s = 0, t = 1;
13     char c = getchar();
14     while( !isdigit(c) ){
15         if( c == '-' ) t = -1;
16         c = getchar();
17     }
18     while( isdigit(c) ){
19         s = s * 10 + c - '0';
20         c = getchar();
21     }
22     return s * t;
23 }
24 
25 void dfs(int x)
26 {
27     used[x] = 1;
28     num[next[x]]--;
29     if( !num[next[x]] ) dfs(next[x]);
30 }
31 
32 void search(int x,int num)
33 {
34     used[x] = 1;
35     if( !used[next[x]] ){
36         search(next[x],num+1);
37     }
38     else if( num != 1 ) ans = min(num,ans);
39 }
40 
41 int main()
42 {
43     int n = read();
44     rep(i,1,n){
45         next[i] = read();
46         num[next[i]]++;
47     }
48     rep(i,1,n){
49         if( !num[i] && !used[i] ) dfs( i );
50     }
51     rep(i,1,n){
52         if( !used[i] )
53         search(i,1);
54     }
55     if( ans > n ) cout<<0<<endl;
56     else cout<<ans<<endl;
57     return 0;
58 }

    接下来,写求强连通块的算法。使用两次dfs的。这个算法在codevs上跑的时间比上面的多一些(但这个求完强连通分量后,还能得到原图的拓扑排序)。

   

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<vector>
 4 #include<stack>
 5 #include<cstring>
 6 #define maxn 200009
 7 #define rep(i,j,k) for(int i = j; i <= k; i++)
 8 using namespace std;
 9 
10 bool used[maxn] = {0};
11 int next[maxn] = {0};
12 vector<int> q[maxn];
13 stack<int> s;
14 int ans = 0xfffffff;
15 
16 int read()
17 {
18     int s = 0, t = 1;
19     char c = getchar();
20     while( !isdigit(c) ){
21         if( c == '-' ) t = -1;
22         c = getchar();
23     }
24     while( isdigit(c) ){
25         s = s * 10 + c - '0';
26         c = getchar();
27     }
28     return s * t;
29 }
30 
31 void dfs(int i)
32 {
33     used[i] = 1;
34     if( !used[next[i]] ) dfs(next[i]); 
35     s.push(i); //注意到最后才把这个元素压入栈。 
36 }
37 
38 int search(int x)
39 {
40     used[x] = 1;
41     int s = q[x].size();
42     int ans = 1;
43     rep(i,0,s-1){
44         if( !used[q[x][i]] )
45         ans += search(q[x][i]);
46     }
47     return ans;
48 }
49 
50 int main()
51 {
52     int n = read();
53     rep(i,1,n){
54         next[i] = read();
55         q[next[i]].push_back(i);
56     }
57     rep(i,1,n){
58         if( !used[i] ) dfs(i);
59     }
60     memset(used,0,sizeof(used));
61     rep(i,1,n){
62         int m = s.top(), k = 0; s.pop();
63         if( !used[m] ){
64             k = search(m);  //注意当 k = 1 时应舍去。
65             if( k <= 1 ) continue;
66             else ans = min(k,ans);
67         }
68     }
69     cout<<ans<<endl;
70     return 0;
71 }

    接下来是tarjan算法求强连通块。这个算法也要比上面第二种快一点。

#include<cstdio>
#include<iostream>
#include<stack>
#define maxn 200009
#define rep(i,j,k) for(int i = j; i <= k; i++)
using namespace std;

int pre[maxn] = {0};
int low[maxn] = {0};
int next[maxn] = {0}; 
int sccno[maxn] = {0};
bool used[maxn] = {0};
int ans = 0xfffffff, cnt = 0;
int sccno_cnt = 0;
stack<int> s;

int read()
{
    int s = 0, t = 1;
    char c = getchar();
    while( !isdigit(c) ){
        if( c == '-' ) t = -1;
        c = getchar();
    }
    while( isdigit(c) ){
        s = s * 10 + c - '0';
        c = getchar();
    }
    return s * t;
}

void dfs(int now)
{
    pre[now] = low[now] = ++cnt;
    used[now] = 1;
    s.push(now);
    if( used[next[now]] ){
        low[now] = min(pre[next[now]],low[now]);
    }
    else if( !sccno[next[now]] ) {
        dfs(next[now]);
        low[now] = min(low[now],low[next[now]]);
    }
    if( pre[now] == low[now] ){
        int num = 0;
        int t = s.top();
        sccno_cnt++;
        while( t != now ){
            sccno[t] = sccno_cnt;
            t = s.top();
            s.pop();
            num++;
        }
        if( num <= 1 ) return;
        else ans = min(ans,num);
    }
}

int main()
{
    int n = read();
    rep(i,1,n){
        next[i] = read();
    }
    rep(i,1,n){
        if( !used[i] ){
            dfs(i);
        }
    }
    cout<<ans<<endl;
    return 0;
}

就这样,求强连通分量的两种常见算法都打了,而且还有针对此题的一种算法。加油!最后说一句,如果把STL的栈改成手打的一个数组,效率会提高一些;可能有几毫秒的提升;不过我也不敢下断言,毕竟比较弱。

    总结,根据codevs和vijos的数据显示,第一个算法跑的时间最短,效率最高(两个网站都能AC),其次是第三个算法(codevs能AC,vijos最后一个点就判了运行错误,其他的点都A了);最差的是第二个算法(codevs能AC,但vijos上只能过两个点,也有可能是我水平有限,无法进一步优化)。

加油,明天会更好。

 

posted on 2015-12-04 21:14  83131  阅读(499)  评论(0编辑  收藏  举报

导航