强连通从入门到精通
资料:
一、模板
(1)tarjan模板
1 #define N 30100 2 //N为最大点数 3 #define M 150100 4 //M为最大边数 5 int n, m;//n m 为点数和边数 6 7 struct Edge{ 8 int from, to, nex; 9 bool sign;//是否为桥 10 }edge[M<<1]; 11 int head[N], edgenum; 12 void add(int u, int v){//边的起点和终点 13 Edge E={u, v, head[u], false}; 14 edge[edgenum] = E; 15 head[u] = edgenum++; 16 } 17 18 int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳) 19 int taj;//连通分支标号,从1开始 20 int Belong[N];//Belong[i] 表示i点属于的连通分支 21 bool Instack[N]; 22 vector<int> bcc[N]; //标号从1开始 23 24 void tarjan(int u ,int fa){ 25 DFN[u] = Low[u] = ++ Time ; 26 Stack[top ++ ] = u ; 27 Instack[u] = 1 ; 28 29 for (int i = head[u] ; ~i ; i = edge[i].nex ){ 30 int v = edge[i].to ; 31 if(DFN[v] == -1) 32 { 33 tarjan(v , u) ; 34 Low[u] = min(Low[u] ,Low[v]) ; 35 if(DFN[u] < Low[v]) 36 { 37 edge[i].sign = 1;//为割桥 38 } 39 } 40 else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 41 } 42 if(Low[u] == DFN[u]){ 43 int now; 44 taj ++ ; bcc[taj].clear(); 45 do{ 46 now = Stack[-- top] ; 47 Instack[now] = 0 ; 48 Belong [now] = taj ; 49 bcc[taj].push_back(now); 50 }while(now != u) ; 51 } 52 } 53 54 void tarjan_init(int all){ 55 memset(DFN, -1, sizeof(DFN)); 56 memset(Instack, 0, sizeof(Instack)); 57 top = Time = taj = 0; 58 for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!! 59 } 60 vector<int>G[N]; 61 int du[N]; 62 void suodian(){ 63 memset(du, 0, sizeof(du)); 64 for(int i = 1; i <= taj; i++)G[i].clear(); 65 for(int i = 0; i < edgenum; i++){ 66 int u = Belong[edge[i].from], v = Belong[edge[i].to]; 67 if(u!=v)G[u].push_back(v), du[v]++; 68 } 69 } 70 void init(){memset(head, -1, sizeof(head)); edgenum=0;}
二、练习
1、【CodeForces 427C】 Checkposts
题意:n(1<=n<=10^5)个城市,m(1<=m<=10^5)条单向的路,现在要放一些保安来管理这n个城市,如果在第i个城市放保安,需要花费a[i](0<=a[i]<=10^9)的钱,如果城市j满足【保安能从i走到j,同时能从j走回到i】那么放在城市i的保安能管理城市j。求在哪些城市放保安能将这n个城市全都治理到,并且所花的钱最少,输出花的钱以及放保安的方案数。
解题思路:模板题,求出每个连通分量的最小花费minx[taj],以及最小花费的个数maxn[taj],花的最少钱是minx[]的和,方案数就是maxn[]的乘积。
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define ll __int64 4 const int mod=1e9+7; 5 #define N 301000 6 //N为最大点数 7 #define M 150100 8 //M为最大边数 9 int n, m;//n m 为点数和边数 10 11 struct Edge{ 12 int from, to, nex; 13 }edge[N<<1]; 14 int head[N], edgenum; 15 void add(int u, int v){//边的起点和终点 16 Edge E={u, v, head[u]}; 17 edge[edgenum] = E; 18 head[u] = edgenum++; 19 } 20 21 int DFN[N], Low[N], Stack[N], top, Time; 22 //Low[u]是点集{u点及以u点为根的子树}中 23 //(所有反向弧)能指向的(离根最近的祖先v)的DFN[v]值(即v点时间戳) 24 int taj;//连通分支标号,从1开始 25 int Belong[N];//Belong[i] 表示i点属于的连通分支 26 bool Instack[N]; 27 ll pp[N], minx[N], maxn[N]; 28 void tarjan(int u ,int fa){ 29 DFN[u] = Low[u] = ++ Time ; 30 Stack[top ++ ] = u ; 31 Instack[u] = 1 ; 32 33 for (int i = head[u] ; ~i ; i = edge[i].nex ){ 34 int v = edge[i].to ; 35 if(DFN[v] == -1) 36 { 37 tarjan(v , u) ; 38 Low[u] = min(Low[u] ,Low[v]) ; 39 } 40 else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 41 } 42 if(Low[u] == DFN[u]){ 43 int now; 44 taj ++ ; 45 do{ 46 now = Stack[-- top] ; 47 Instack[now] = 0 ; 48 Belong [now] = taj ; 49 if(minx[taj]>pp[now]) minx[taj]=pp[now], maxn[taj]=0; 50 if(minx[taj]==pp[now]) maxn[taj]++; 51 }while(now != u) ; 52 } 53 } 54 55 void tarjan_init(int all){ 56 memset(DFN, -1, sizeof(DFN)); 57 memset(Instack, 0, sizeof(Instack)); 58 memset(minx, 0x3f3f3f3f, sizeof(minx)); 59 memset(maxn, 0, sizeof(maxn)); 60 top = Time = taj = 0; 61 for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!! 62 } 63 void init(){memset(head, -1, sizeof(head)); edgenum=0;} 64 int main(){ 65 scanf("%d", &n); 66 init(); 67 for(int i=1; i<=n; i++) scanf("%d", &pp[i]); 68 scanf("%d", &m); 69 int u, v; 70 for(int i=0; i<m; i++){ 71 scanf("%d%d", &u, &v); 72 add(u, v); 73 } 74 tarjan_init(n); 75 ll ans=0, sum=1; 76 for(int i=1; i<=taj; i++){ 77 ans+=minx[i]; 78 sum = (sum*maxn[i])%mod; 79 } 80 printf("%I64d %I64d\n", ans, sum); 81 return 0; 82 }
2、【hdu 1827】Proving Equivalences
题意:有n(1<=n<=1000)个人需要通知,小A每通知一个人就要花费a[i]的花费,有m(1<=m<=2000)组关系,每组关系只能单向联系,问小A至少需要通知几个人,要花费多少钱?
解题思路:连通分量缩点,一个连通分量只需通知一人,求出通知每个连通分量的人最少话费,入度为0的连通分量肯定要通知,因为没人通知他,否则就不用通知
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <string> 6 #include <vector> 7 using namespace std; 8 #define N 30100 9 #define M 150100 10 int n, m; 11 12 struct Edge{ 13 int from, to, nex; 14 }edge[N<<1]; 15 int head[N], edgenum; 16 int a[N]; 17 void add(int u, int v){//边的起点和终点 18 Edge E={u, v, head[u]}; 19 edge[edgenum] = E; 20 head[u] = edgenum++; 21 } 22 23 int minx[N], DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳) 24 int taj;//连通分支标号,从1开始 25 int Belong[N];//Belong[i] 表示i点属于的连通分支 26 bool Instack[N]; 27 vector<int> bcc[N]; //标号从1开始 28 29 void tarjan(int u ,int fa){ 30 DFN[u] = Low[u] = ++ Time ; 31 Stack[top ++ ] = u ; 32 Instack[u] = 1 ; 33 34 for (int i = head[u] ; ~i ; i = edge[i].nex ){ 35 int v = edge[i].to ; 36 if(DFN[v] == -1) 37 { 38 tarjan(v , u) ; 39 Low[u] = min(Low[u] ,Low[v]) ; 40 } 41 else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 42 } 43 if(Low[u] == DFN[u]){ 44 int now; 45 taj ++ ; bcc[taj].clear(); 46 int k=0x3f3f3f3f; 47 do{ 48 now = Stack[-- top] ; 49 Instack[now] = 0 ; 50 Belong [now] = taj ; 51 k = min(k, a[now]); 52 bcc[taj].push_back(now); 53 }while(now != u) ; 54 minx[taj] = k; 55 } 56 } 57 58 void tarjan_init(int all){ 59 memset(DFN, -1, sizeof(DFN)); 60 memset(Instack, 0, sizeof(Instack)); 61 top = Time = taj = 0; 62 for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!! 63 } 64 vector<int>G[N]; 65 int du[N]; 66 void suodian(){ 67 memset(du, 0, sizeof(du)); 68 for(int i = 1; i <= taj; i++)G[i].clear(); 69 for(int i = 0; i < edgenum; i++){ 70 int u = Belong[edge[i].from], v = Belong[edge[i].to]; 71 //一开始我是这样求minx[]的,但是这里是枚举边的,没法处理单独的点,会wa 72 /*minx[u] = min(minx[u], a[edge[i].from]); 73 minx[v] = min(minx[v], a[edge[i].to]); */ 74 if(u!=v)G[u].push_back(v), du[v]++; 75 } 76 } 77 void init(){memset(head, -1, sizeof(head)); edgenum=0;} 78 int main(){ 79 int n, m, u, v; 80 while(~scanf("%d%d", &n, &m)){ 81 init(); 82 for(int i=1; i<=n; i++) scanf("%d", &a[i]); 83 for(int i=0; i<m; i++){ 84 scanf("%d%d", &u, &v); 85 add(u, v); 86 } 87 tarjan_init(n); 88 suodian(); 89 int num=0, ans=0; 90 for(int i=1; i<=taj; i++){ 91 if(du[i]==0){ 92 num++, ans+=minx[i]; 93 } 94 } 95 printf("%d %d\n", num, ans); 96 } 97 return 0; 98 }
3、【poj 2762】Going from u to v or from v to u?
题意:n(0<n<1001)个城市,m(m<6000)条单向路,如果任意两个城市u,v满足【u能走到v || v能走到u】就输出Yes,否则就输出No
解题思路:一个连通分量是肯定满足条件的,接下来就是判断任意两个连通分量之间是不是满足题意,这个好办,连通分量缩点,然后求缩点后形成的树的直径,如果直径的大小就是连通分量的个数,那就是Yes,也就是缩点后的连通分量是一条链,没有分支,否则必定有两个点之间是无法到达的
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <string> 6 #include <vector> 7 using namespace std; 8 const int N=1e4+10; 9 const int M=1e5+10; 10 int n, m; 11 struct Edge{ 12 int from, to, nxt; 13 }edge[M<<1]; 14 int head[N], edgenum; 15 16 void init(){ 17 memset(head, -1, sizeof(head)); 18 edgenum=0; 19 } 20 void add(int u, int v){ 21 Edge E = {u, v, head[u]}; 22 edge[edgenum] = E; 23 head[u]=edgenum++; 24 } 25 int DFN[N], Stack[N], Low[N], top, time; 26 int taj; 27 int Belong[N]; 28 bool Instack[N]; 29 void tarjan(int u, int fa){ 30 DFN[u] = Low[u] = ++time; 31 Stack[top++] = u; 32 Instack[u] = 1; 33 34 for(int i=head[u]; ~i; i=edge[i].nxt){ 35 int v = edge[i].to; 36 if(DFN[v]==-1){ 37 tarjan(v, u); 38 Low[u] = min(Low[u], Low[v]); 39 } 40 else if(Instack[v]) Low[u] = min(Low[u], DFN[v]); 41 } 42 if(Low[u] == DFN[u]){ 43 taj++; 44 int now; 45 do{ 46 now = Stack[--top]; 47 Instack[now] = 0; 48 Belong[now] = taj; 49 50 }while(now != u); 51 } 52 } 53 void tarjan_init(int all){ 54 memset(DFN, -1, sizeof(DFN)); 55 memset(Instack, 0, sizeof(Instack)); 56 top = time = taj = 0; 57 for(int i=1; i<=all; i++){ 58 if(DFN[i]==-1) tarjan(i, i); 59 } 60 } 61 vector<int>G[N]; 62 int du[N]; 63 void suodian(){ 64 memset(du, 0, sizeof(du)); 65 for(int i=1; i<=n; i++) G[i].clear(); 66 for(int i=0; i<edgenum; i++){ 67 int uu=Belong[edge[i].from], vv=Belong[edge[i].to]; 68 if(uu != vv){ 69 G[uu].push_back(vv); 70 du[vv]++; 71 } 72 } 73 } 74 75 int dis[N], id, maxn, ans; 76 void dfs(int u, int fa){ 77 for(int i=0; i<G[u].size(); i++){ 78 int v = G[u][i]; 79 if(v != fa){ 80 dis[v] = dis[u]+1; 81 if(dis[v]>maxn){ 82 id=v, maxn = dis[v], ans++; 83 } 84 } 85 dfs(v, u); 86 } 87 } 88 int main(){ 89 int t; 90 scanf("%d", &t); 91 while(t--){ 92 scanf("%d%d", &n, &m); 93 init(); 94 int u, v; 95 for(int i=0; i<m; i++){ 96 scanf("%d%d", &u, &v); 97 add(u, v); 98 } 99 tarjan_init(n); 100 suodian(); 101 ans = 0; 102 for(int i=1; i<=taj; i++) { 103 if(du[i]==0) id=i, ans++; 104 } 105 if(ans>1) { 106 printf("No\n"); 107 continue; 108 } 109 memset(dis, 0, sizeof(dis)); 110 dis[id] = 1; 111 maxn=1; 112 dfs(id, -1); 113 if(maxn==taj) printf("Yes\n"); 114 else printf("No\n"); 115 } 116 return 0; 117 }
4、【hdu 2767】Proving Equivalences &&【hdu 3836】Equivalent Sets &&【POJ 1236】Network of Schools
题意:已知图上有n(n<=2*10^4)个点,m(m<=5*10^4)条单向边,问至少加几条边让整个图变成强连通。
解题思路:Tarjan入门经典题,用tarjan缩点,然后就变成一个有向无环图(DAG)了。
我们要考虑的问题是让它变成强连通,让DAG变成强连通就是把尾和头连起来,也就是入度和出度为0的点。
统计DAG入度和出度,然后计算头尾,最大的那个就是所求。
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <string> 6 #include <vector> 7 using namespace std; 8 const int N=2*1e4+10; 9 const int M=5*1e4+10; 10 int n, m; 11 struct Edge{ 12 int from, to, nxt; 13 }edge[M]; 14 int head[M], edgenum; 15 void init(){ 16 memset(head, -1, sizeof(head)); 17 edgenum = 0; 18 } 19 void addedge(int u, int v){ 20 Edge E = {u, v, head[u]}; 21 edge[edgenum] = E; 22 head[u] = edgenum++; 23 } 24 int DFN[N], Low[N], Belong[N], taj, top, time; 25 int Stack[N]; 26 bool Instack[N]; 27 void tarjan(int u, int fa){ 28 DFN[u] = Low[u] = ++time; 29 Stack[top++]=u; 30 Instack[u]=1; 31 for(int i=head[u]; ~i; i=edge[i].nxt){ 32 int v = edge[i].to; 33 if(DFN[v] == -1){ 34 tarjan(v, u); 35 Low[u] = min(Low[u], Low[v]); 36 } 37 else if(Instack[v]) Low[u] = min(Low[u], DFN[v]); 38 } 39 if(DFN[u] == Low[u]){ 40 int now; 41 taj++; 42 do{ 43 now = Stack[--top]; 44 Belong[now]=taj; 45 Instack[now]=0; 46 }while(now!=u); 47 } 48 } 49 void tarjan_init(int all){ 50 memset(DFN, -1, sizeof(DFN)); 51 memset(Instack, 0, sizeof(Instack)); 52 taj = top = time = 0; 53 for(int i=1; i<=all; i++){ 54 if(DFN[i] == -1) tarjan(i, i); 55 } 56 } 57 vector<int>G[N]; 58 int du[N], chu[N]; 59 void suodian(){ 60 memset(du, 0, sizeof(du)); 61 memset(chu, 0, sizeof(chu)); 62 for(int i=1; i<=taj; i++) G[i].clear(); 63 for(int i=0; i<edgenum; i++){ 64 int uu=Belong[edge[i].from], vv=Belong[edge[i].to]; 65 if(uu!=vv) G[uu].push_back(vv), du[vv]++, chu[uu]++; 66 } 67 } 68 int main(){ 69 while(~scanf("%d%d", &n, &m)){ 70 init(); 71 int u, v; 72 for(int i=0; i<m; i++){ 73 scanf("%d%d", &u, &v); 74 addedge(u, v); 75 } 76 tarjan_init(n); 77 suodian(); 78 int num_chu=0, num_ru=0; 79 if(taj>1){ 80 for(int i=1; i<=taj; i++){ 81 if(chu[i]==0) num_chu++; 82 if(du[i]==0) num_ru++; 83 } 84 } 85 printf("%d\n", max(num_chu, num_ru)); 86 } 87 return 0; 88 }
题意:有N个人,M条边,边(u,v)表示u的年龄不小于v,若把这N个人分为很多组,要求每一组中的年龄无法相互比较,求组数最小值。
偏序:图中每次删边后入度为0的点有多个,图中的点无法比较大小
全序:图中每次删边后入读为0的点只有一个,图上的点的先后关系只有一种。
解题思路:简单来说就是先缩点,记录点权值,求一条链上的最大权值和
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <string> 6 #include <vector> 7 #include <queue> 8 using namespace std; 9 const int N=1e5+10; 10 const int M=3*1e5+10; 11 int n, m; 12 struct Edge{ 13 int from, to, nxt; 14 }edge[M]; 15 int head[M], edgenum; 16 void init(){ 17 memset(head, -1, sizeof(head)); 18 edgenum = 0; 19 } 20 void addedge(int u, int v){ 21 Edge E = {u, v, head[u]}; 22 edge[edgenum] = E; 23 head[u] = edgenum++; 24 } 25 int DFN[N], Low[N], Belong[N], taj, top, Time; 26 int Stack[N], num[N]; 27 bool Instack[N]; 28 void tarjan(int u, int fa){ 29 DFN[u] = Low[u] = ++Time; 30 Stack[top++]=u; 31 Instack[u]=1; 32 for(int i=head[u]; ~i; i=edge[i].nxt){ 33 int v = edge[i].to; 34 if(DFN[v] == -1){ 35 tarjan(v, u); 36 Low[u] = min(Low[u], Low[v]); 37 } 38 else if(Instack[v]) Low[u] = min(Low[u], DFN[v]); 39 } 40 if(DFN[u] == Low[u]){ 41 int now; 42 taj++; 43 do{ 44 now = Stack[--top]; 45 Belong[now]=taj; 46 Instack[now]=0; 47 num[taj]++; 48 }while(now!=u); 49 } 50 } 51 void tarjan_init(int all){ 52 memset(DFN, -1, sizeof(DFN)); 53 memset(Instack, 0, sizeof(Instack)); 54 memset(num, 0, sizeof(num)); 55 taj = top = Time = 0; 56 for(int i=1; i<=all; i++){ 57 if(DFN[i] == -1) tarjan(i, i); 58 } 59 } 60 vector<int>G[N]; 61 int du[N], chu[N]; 62 void suodian(){ 63 memset(du, 0, sizeof(du)); 64 memset(chu, 0, sizeof(chu)); 65 for(int i=1; i<=taj; i++) G[i].clear(); 66 for(int i=0; i<edgenum; i++){ 67 int uu=Belong[edge[i].from], vv=Belong[edge[i].to]; 68 if(uu!=vv) G[uu].push_back(vv), du[vv]++, chu[uu]++; 69 } 70 } 71 int ans, maxn, lian; 72 int dis[N]; 73 queue<int>q; 74 void BFS(){//拓扑排序 75 while(!q.empty()) q.pop(); 76 memset(dis, 0, sizeof(dis)); 77 maxn=0, lian=0; 78 for(int i=1; i<=taj; i++){ 79 if(du[i]==0) { 80 q.push(i); 81 dis[i]=num[i]; 82 } 83 } 84 int u, v; 85 while(!q.empty()){ 86 u=q.front();q.pop(); 87 if((int)G[u].size()==0){ 88 maxn = max(maxn, dis[u]); 89 continue; 90 } 91 for(int i=0; i<G[u].size(); i++){ 92 int v=G[u][i]; 93 du[v]--; 94 dis[v] = max(dis[v], dis[u]+num[v]); 95 if(du[v]==0) q.push(v); 96 } 97 } 98 printf("%d\n", maxn); 99 } 100 int main(){ 101 int n, m; 102 while(~scanf("%d%d", &n, &m)){ 103 init(); 104 int u, v; 105 for(int i=0; i<m; i++){ 106 scanf("%d%d", &u, &v); 107 addedge(u, v); 108 } 109 tarjan_init(n); 110 suodian(); 111 BFS(); 112 } 113 return 0; 114 }
题意:n m表示n个节点,m(0<=m<=9900)条边,下面m行a b 表示a-b点有一条有向边,给定有向图,删去一个点后,可以求出该图中强连通分量中最大的点数,问:删去某点后,最大点数 最小是多少(一个连通分量中至少要有2个点,否则该连通分量的点数记为0)
解题思路:枚举删点,强连通求最大分量
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <string> 6 using namespace std; 7 const int N=1e5+10; 8 const int M=3*1e5+10; 9 int n, m; 10 struct Edge{ 11 int from, to, nxt; 12 }edge[M]; 13 int head[M], edgenum; 14 void init(){ 15 memset(head, -1, sizeof(head)); 16 edgenum = 0; 17 } 18 void addedge(int u, int v){ 19 Edge E = {u, v, head[u]}; 20 edge[edgenum] = E; 21 head[u] = edgenum++; 22 } 23 int DFN[N], Low[N], Belong[N], taj, top, Time; 24 int Stack[N], num[N]; 25 bool Instack[N]; 26 int maxn_min, ans; 27 void tarjan(int u, int fa, int id){ 28 DFN[u] = Low[u] = ++Time; 29 Stack[top++]=u; 30 Instack[u]=1; 31 for(int i=head[u]; ~i; i=edge[i].nxt){ 32 int v = edge[i].to; 33 if(v==id) continue; 34 if(DFN[v] == -1){ 35 tarjan(v, u, id); 36 Low[u] = min(Low[u], Low[v]); 37 } 38 else if(Instack[v]) Low[u] = min(Low[u], DFN[v]); 39 } 40 if(DFN[u] == Low[u]){ 41 int now; 42 taj++; 43 do{ 44 now = Stack[--top]; 45 Belong[now]=taj; 46 Instack[now]=0; 47 num[taj]++; 48 }while(now!=u); 49 ans = max(ans, num[taj]); 50 } 51 } 52 void tarjan_init(int all, int id){ 53 memset(DFN, -1, sizeof(DFN)); 54 memset(Instack, 0, sizeof(Instack)); 55 memset(num, 0, sizeof(num)); 56 taj = top = Time = 0; 57 for(int i=0; i<all; i++){ 58 if(i==id) continue; 59 if(DFN[i] == -1) tarjan(i, i, id); 60 } 61 } 62 int main(){ 63 while(~scanf("%d%d", &n, &m)){ 64 init(); 65 int u, v; 66 for(int i=0; i<m; i++){ 67 scanf("%d%d", &u, &v); 68 addedge(u, v); 69 } 70 maxn_min=1e9; 71 for(int i=0; i<n; i++){ 72 ans=0; 73 tarjan_init(n, i); 74 if(ans<2) ans=0; 75 maxn_min=min(maxn_min, ans); 76 } 77 maxn_min = (maxn_min==1e9)?0:maxn_min; 78 printf("%d\n", maxn_min); 79 } 80 return 0; 81 }