zoukankan      html  css  js  c++  java
  • 强连通分量的题目列表(一)

    强连通的概念:有向图,其中每个点都相互可达。

    强连通有两个算法,Kosaraju算法和tarjan算法, 但是tarjan算法更快,一般用这个。

    题目列表:

    ①基础题 LA 4287    和POJ3352 边双连通 最后的ans的判断方法做一下区别 双连通(一)

    ②强连通+dp UVA11324(二)

    ③强连通+数学 cf 711 div2 D(三)

    ④强连通+缩点 HDU 3836 (四)

    一:等价性证明 LA 4287 蓝书 322

    思路:强连通分量取出来以后看成一个点,于是就是一个DAG,然后让DAG最后变成强连通即可。然后要满足每个点的入度和出度都存在,这样才能表达等价。ans=max(入度不存在的个数,出度不存在的个数)

    注意一个trick,如果本来就是强连通了,ans就是0

    //看看会不会爆int! 或者绝对值问题。
    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define all(a) a.begin(), a.end()
    const int maxn = 20000 + 5;
    vector<int> G[maxn];
    int pre[maxn], low[maxn], c[maxn];
    int n, m;
    stack<int> s;
    int dfstime, scc_cnt;
    
    void dfs(int u, int fa){
        pre[u] = low[u] = ++dfstime;
        int len = G[u].size();
        s.push(u);
        for (int i = 0; i < len; i++){
            int v = G[u][i];
            if (pre[v] == -1){
                dfs(v, u);
                low[u] = min(low[u], low[v]);
            }
            /*else if (low[v] < pre[u] && v != fa){///双连通里面,这里是通过反向边更新的
                low[u] = min(low[u], low[v]);
            }*/
            else if (!c[v]){///下面为什么是pre[v]而不是low[v](两者都可以)
                low[u] = min(low[u], pre[v]);///因为环装最后总会变回来,一样的
            }
        }
        if (low[u] == pre[u]){
            scc_cnt++;
            while (true){
                int x = s.top(); s.pop();
                c[x] = scc_cnt;
                if (x == u) break;
            }
        }
        return ;
    }
    int in[maxn], ou[maxn];
    int main(){
        int t; cin >> t;
        while (t--){
            scanf("%d%d", &n, &m);
            for (int i = 1; i <= n; i++) G[i].clear();
            for (int i = 1; i <= m; i++){
                int u, v; scanf("%d%d", &u, &v);
                G[u].pb(v);
            }
            memset(c, 0, sizeof(c));
            memset(pre, -1, sizeof(pre));
            memset(low, 0, sizeof(low));
            dfstime = scc_cnt = 0;
            for (int i = 1; i <= n; i++){
                if (pre[i] == -1){
                    dfs(i, -1);
                }
            }
            memset(in, 0, sizeof(in));
            memset(ou, 0, sizeof(ou));
            for (int i = 1; i <= n; i++){
                int len = G[i].size();
                for (int j = 0; j < len; j++){
                    int v = G[i][j];
                    if (c[i] != c[v]) {
                        in[c[i]]++;
                        ou[c[v]]++;
                    }
                }
            }
            int ans1 = 0, ans2 = 0;
            for (int i = 1; i <= scc_cnt; i++){
                if (in[i] == 0) ans1++;
                if (ou[i] == 0) ans2++;
            }
            if (scc_cnt == 1) ans1 = ans2 = 0;
            //printf("scc_cnt = %d
    ", scc_cnt);
            printf("%d
    ", max(ans1, ans2));
        }
        return 0;
    }
    View Code

    学习:注意dfs的else if条件,注意trick条件

    二:最大团 UVA11324 蓝书323

    题目大意:给一张有向图G,求一个结点数最大的节点集,是的该节点集中任意两个节点u和v,满足要么u可以到v,要么v可以到u或者两者可以相互可达。

    思路:有向图,我们先求出强连通,然后让图变成一个DAG,然后在dp一下就行了。dp[i]表示从i出发的最长的距离

      1 //看看会不会爆int! 或者绝对值问题。
      2 #include <bits/stdc++.h>
      3 using namespace std;
      4 #define LL long long
      5 #define pb push_back
      6 #define mk make_pair
      7 #define fi first
      8 #define se second
      9 #define all(a) a.begin(), a.end()
     10 const int maxn = 1000 + 5;
     11 vector<int> G[maxn];
     12 int n, m, dfstime, scc_cnt;
     13 int pre[maxn], sccnu[maxn], low[maxn];
     14 stack<int> s;
     15 
     16 void dfs(int u){
     17     low[u] = pre[u] = ++dfstime;
     18     int len = G[u].size();
     19     s.push(u);
     20     for (int i = 0; i < len; i++){
     21         int v = G[u][i];
     22         if (pre[v] == -1){
     23             dfs(v);
     24             low[u] = min(low[u], low[v]);
     25         }
     26         else if (!sccnu[v]){
     27             low[u] = min(low[u], pre[v]);
     28         }
     29     }
     30     if (low[u] == pre[u]){
     31         //printf("u = %d
    ", u);
     32         scc_cnt++;
     33         while (true){
     34             int x = s.top(); s.pop();
     35             sccnu[x] = scc_cnt;
     36             if (x == u) break;
     37         }
     38     }
     39     return ;
     40 }
     41 int cnt[maxn], dp[maxn], vis[maxn];
     42 int a[maxn][maxn];
     43 
     44 int ans_dfs(int u){
     45     //printf("u = %d
    ", u);
     46     int &ans = dp[u];
     47     if (ans > 0) return ans;
     48     ans = cnt[u];
     49     for (int i = 1; i <= scc_cnt; i++){
     50         if (a[u][i] && i != u){
     51             //printf("u = %d i = %d
    ", u, i);
     52             //printf("dp[u] = %d
    ", dp[u]);
     53             ans = max(ans, ans_dfs(i) + cnt[u]);
     54             //printf("ans = %d, cnt[%d] = %d
    ", ans, i, cnt[i]);
     55         }
     56     }
     57     return ans;
     58 }
     59 
     60 
     61 int main(){
     62     int t; cin >> t;
     63     while (t--){
     64         scanf("%d%d", &n, &m);
     65         for (int i = 1; i <= n; i++){
     66             G[i].clear();
     67         }
     68         for (int i = 1; i <= m; i++){
     69             int u, v; scanf("%d%d", &u, &v);
     70             G[u].pb(v);
     71         }
     72         memset(pre, -1, sizeof(pre));
     73         memset(sccnu, 0, sizeof(sccnu));
     74         memset(low, 0, sizeof(low));
     75         dfstime = scc_cnt = 0;
     76         for (int i = 1; i <= n; i++){
     77             if (pre[i] == -1) dfs(i);
     78         }
     79         memset(cnt, 0, sizeof(cnt));
     80         memset(a, 0, sizeof(a));
     81         for (int i = 1; i <= n; i++){
     82             int x = sccnu[i];
     83             cnt[x]++;
     84             int len = G[i].size();
     85             for (int j = 0; j < len; j++){
     86                 int v = G[i][j];
     87                 int y = sccnu[v];
     88                 if (x == y) continue;
     89                 a[x][y] = 1;
     90                 //printf("%d %d
    ", x, y);
     91             }
     92         }
     93         memset(dp, 0, sizeof(dp));
     94         int ans = 0;
     95         for (int i = 1; i <= scc_cnt; i++) {
     96             ans_dfs(i);
     97         }
     98         for (int i = 1; i <= scc_cnt; i++) {
     99             //printf("dp[%d] = %d
    ", i, dp[i]);
    100             ans = max(ans, dp[i]);
    101         }
    102 
    103         printf("%d
    ", ans);
    104     }
    105     return 0;
    106 }
    View Code

    三:codeforces 711 div2 D http://codeforces.com/contest/711/problem/D

    题目大意:给你一个有向图,每个节点的出度都为1,定义confusing road为这个图中存在环,目前有一种操作:将一条路的方向翻转,翻转次数任意。问有几种翻转操作能让该图中不存在环。

    思路:因为出度为1,所以只存在简单环,只需要用tarjan把所有环取出来,然后每次乘以2^x - 2即可,x表示环中的点数,减2表示该点数情况下形成的环的种类数只有两种。然后x=1的时候特判一下就行了。

    //看看会不会爆int! 或者绝对值问题。
    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define all(a) a.begin(), a.end()
    const int maxn = 200000 + 5;
    vector<int> G[maxn];
    int n, m, dfstime, scc_cnt;
    int pre[maxn], sccnu[maxn], low[maxn];
    stack<int> s;
    
    void dfs(int u){
        low[u] = pre[u] = ++dfstime;
        int len = G[u].size();
        s.push(u);
        for (int i = 0; i < len; i++){
            int v = G[u][i];
            if (pre[v] == -1){
                dfs(v);
                low[u] = min(low[u], low[v]);
            }
            else if (!sccnu[v]){
                low[u] = min(low[u], pre[v]);
            }
        }
        if (low[u] == pre[u]){
            scc_cnt++;
            while (true){
                int x = s.top(); s.pop();
                sccnu[x] = scc_cnt;
                if (x == u) break;
            }
        }
        return ;
    }
    
    LL cnt[maxn];
    const LL mod = 1e9 + 7;
    
    LL cal(LL x, LL n){
        LL ans = 1;
        while (n){
            if (n & 1) ans = ans * x % mod;
            n >>= 1; x = x * x % mod;
        }
        return ans - 2;
    }
    int main(){
        scanf("%d", &n);
        for (int i = 1; i <= n; i++){
            G[i].clear();
        }
        for (int i = 1; i <= n; i++){
            int v;
            scanf("%d", &v);
            G[i].pb(v);
        }
        memset(pre, -1, sizeof(pre));
        memset(sccnu, 0, sizeof(sccnu));
        memset(low, 0, sizeof(low));
        dfstime = scc_cnt = 0;
        for (int i = 1; i <= n; i++){
            if (pre[i] == -1) dfs(i);
        }
        for (int i = 1; i <= n; i++){
            cnt[sccnu[i]]++;
        }
        LL ans = 1;
        for (int i = 1; i <= scc_cnt; i++){
            if (cnt[i] == 1) ans = ans * 2 % mod;
            else {
                ans = ans * cal(2, cnt[i]) % mod;
            }
        }
        printf("%I64d
    ", ans);
        return 0;
    }
    View Code

    四:HDU 3836

    题目大意:将题目中的集合转换为顶点,A集合是B集合的子集,转换为一条有向边<A,B>,即题目给我们一个有向图,问最少需要添加多少条边使之成为强连通图。 

    思路:强连通以后缩点,然后定义入度为in,出度为out,答案为max(in_cnt, out_cnt).

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include <bits/stdc++.h>
    using namespace std;
    #pragma comment(linker,"/STACK:102400000,102400000")
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define haha printf("haha
    ")
    const int maxn = 50000 + 5;
    vector<int> G[maxn];
    int pre[maxn], low[maxn], c[maxn];
    int n, m;
    stack<int> s;
    int dfstime, scc_cnt;
    
    void dfs(int u, int fa){
        pre[u] = low[u] = ++dfstime;
        int len = G[u].size();
        s.push(u);
        for (int i = 0; i < len; i++){
            int v = G[u][i];
            if (pre[v] == -1){
                dfs(v, u);
                low[u] = min(low[u], low[v]);
            }
            else if (!c[v]){///下面为什么是pre[v]而不是low[v](两者都可以)
                low[u] = min(low[u], pre[v]);///因为环装最后总会变回来,一样的
            }
        }
        if (low[u] == pre[u]){
            scc_cnt++;
            while (true){
                int x = s.top(); s.pop();
                c[x] = scc_cnt;
                if (x == u) break;
            }
        }
        return ;
    }
    int in[maxn], out[maxn];
    void init(){
        for (int i = 1; i <= n; i++) G[i].clear();
        memset(in, 0, sizeof(in));
        memset(out, 0, sizeof(out));
        memset(pre, -1, sizeof(pre));
        memset(low, 0, sizeof(low));
        memset(c, 0, sizeof(c));
        while (!s.empty()) s.pop();
        dfstime = scc_cnt = 0;
    }
    
    int solve(){
        for (int i = 1; i <= m; i++){
            int u, v; scanf("%d%d", &u, &v);
            G[u].push_back(v);
        }
        for (int i = 1; i <= n; i++){
            if (pre[i] == -1) dfs(i, -1);
        }
        if (scc_cnt == 1) return 0;
        for (int i = 1; i <= n; i++){
            for (int j = 0; j < G[i].size(); j++){
                int v = G[i][j], u = i;
                if (c[v] != c[u]) out[c[u]]++, in[c[v]]++;
            }
        }
        int t1 = 0, t2 = 0;
        for (int i = 1; i <= scc_cnt; i++){
            if (out[i] == 0) t1++;
            if (in[i] == 0) t2++;
        }
        return max(t1, t2);
    }
    
    int main(){
        while (scanf("%d%d", &n, &m) != EOF){
            if (n == 1) {printf("0
    "); continue;}
            init();
            printf("%d
    ", solve());
        }
        return 0;
    }
    View Code

    关键:缩点

    五:

    六:

  • 相关阅读:
    Sqlserver2005迁移至Oracle系列之二:生成存储过程
    Sqlserver:在sql2005查看索引的碎片统计情况,并给出处理意见的自动化批处理脚本
    Sqlserver:利用Sqlserver2005的数据库触发器开发的数据库审核追踪系统,可以跟踪对象的修改信息及修改源代码
    Sqlserver2005迁移至Oracle系列之一:生成表
    中国软件业的现状浅析
    Java之异常与错误的区别及java的异常体系
    SQL优化34条
    swftools转换文件时线程堵塞问题的解决方法
    中国软件开发需要注意的地方
    Flex 应用内存泄露的分析与诊断
  • 原文地址:https://www.cnblogs.com/heimao5027/p/5815900.html
Copyright © 2011-2022 走看看