强连通的概念:有向图,其中每个点都相互可达。
强连通有两个算法,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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//看看会不会爆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; }
学习:注意dfs的else if条件,注意trick条件
二:最大团 UVA11324 蓝书323
题目大意:给一张有向图G,求一个结点数最大的节点集,是的该节点集中任意两个节点u和v,满足要么u可以到v,要么v可以到u或者两者可以相互可达。
思路:有向图,我们先求出强连通,然后让图变成一个DAG,然后在dp一下就行了。dp[i]表示从i出发的最长的距离
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
三:codeforces 711 div2 D http://codeforces.com/contest/711/problem/D
题目大意:给你一个有向图,每个节点的出度都为1,定义confusing road为这个图中存在环,目前有一种操作:将一条路的方向翻转,翻转次数任意。问有几种翻转操作能让该图中不存在环。
思路:因为出度为1,所以只存在简单环,只需要用tarjan把所有环取出来,然后每次乘以2^x - 2即可,x表示环中的点数,减2表示该点数情况下形成的环的种类数只有两种。然后x=1的时候特判一下就行了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//看看会不会爆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; }
四:HDU 3836
题目大意:将题目中的集合转换为顶点,A集合是B集合的子集,转换为一条有向边<A,B>,即题目给我们一个有向图,问最少需要添加多少条边使之成为强连通图。
思路:强连通以后缩点,然后定义入度为in,出度为out,答案为max(in_cnt, out_cnt).
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//看看会不会爆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; }
关键:缩点
五:
六: