• 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上只能过两个点,也有可能是我水平有限,无法进一步优化)。

    加油,明天会更好。

     

    人一我十,人十我万!追逐青春的梦想,怀着自信的心,永不放弃!仿佛已看到希望,尽管还在远方
  • 相关阅读:
    clientHeight、offsetHeight和scrollHeight
    关于网页元素的定位问题及offsetParent的讨论
    SQL关闭自增长列标识:SET IDENTITY_INSERT
    动态添加对Web Service的引用
    关于asp.net导出Excel
    scrollTop、scrollLeft、scrollWidth、scrollHeight
    ADO.NET实体数据模型
    不能加断点 单步调试被跳过
    无法启动程序“http://localhost:3303/Default.aspx”
    asp.net本质论学习笔记第一章
  • 原文地址:https://www.cnblogs.com/83131yyl/p/5020528.html
Copyright © 2020-2023  润新知