强连通缩点
其法有三:Kosaraju,Trajan,Gabow
Kosaraju时间稍慢,需要建反边+两次dfs,但是第二次dfs的时候就可以顺便把新图建出来
Gabow算法两个栈比较恶心,效率最高,比Trajan少了一些更新low数组的过程
三者代码量相差无几,60上下
个人感觉还是Trajan最实用,而且low的计算和割边割点还有联系
我觉得HDU2767这题可以来试强连通分量模板
题目大意是给你一个有向图,问你至少加几条边让整个图变成强连通
解法:缩点后统计没有出度的和没有入度的点个个数,两者取最大值
同时推荐byvoid的网站
BYV大牛的tarjan算法介绍,写的非常详细
- Kosaraju缩点模板,328MS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
#define M 20001 vector <int> edge[M];//原图的边 vector <int> opp[M];//反图的边 vector <int> now[M];//新图的边 int hash[M];//用来hash新图的重边 bool vis[M];//有无访问过该点 int post[M];//时间戳对应的点 int idx[M];//点在新图的ID int n;//原图大小 int nn;//新图大小 int timestamp;//时间戳 void dfs(int u) {//第一个dfs确定时间戳 vis[u] = true; FF(i,SZ(edge[u])) { if(vis[ edge[u][i] ]) continue; dfs(edge[u][i]); } post[timestamp++] = u; } void dfs2(int u) {//第二个反边dfs确定连通块 vis[u] = true; idx[u] = nn; FF(i,SZ(opp[u])) { int &v = opp[u][i]; if(vis[v]) { if(idx[v] != nn && hash[nn] != idx[v]) { hash[nn] = idx[v];//注意hash掉重复的边 now[idx[v]].push_back(nn); } } else { dfs2(v); } } } void Kosaraju() { CC(vis,false); timestamp = 0; FF(i,n) { if(vis[i]) continue; dfs(i); } CC(vis,false); CC(hash,-1); nn = 0; FFD(i,n) {//按时间戳从大到小搜 if(vis[post[i]]) continue; now[nn].clear(); dfs2(post[i]); nn ++; } }
- Tarjan缩点模板,234MS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
#define M 20001 vector <int> edge[M];//原图的边 vector <int> now[M];//新图的边 int hash[M];//用来hash新图的重边 bool vis[M];//有无访问过该点 int pre[M],Index;//访问顺序 int low[M];//能追溯到的最早的次序 int idx[M];//点在新图的ID int ss[M],top;//放节点的栈 bool InStack[M];//元素是不是在栈里 int n;//原图大小 int nn;//新图大小 void dfs(int u) { vis[u] = true; pre[u] = low[u] = Index ++; InStack[u] = true; ss[++top] = u; FF(i,SZ(edge[u])) { int v = edge[u][i]; if(!vis[v]) { dfs(v); checkmin(low[u] , low[v]); } else if(InStack[v]) { checkmin(low[u] , pre[v]); } } if(pre[u] == low[u]) { int v = -1; while(u != v) { v = ss[top--]; InStack[v] = false; idx[v] = nn; } now[nn++].clear(); } } void Trajan() { CC(vis,false); Index = 0; top = 0; nn = 0; FF(i,n) { if(vis[i]) continue; dfs(i); } CC(hash,-1); FF(i,n) { FF(j,SZ(edge[i])) { int v = edge[i][j]; if(idx[i] != idx[v] && hash[idx[i]] != idx[v]) { hash[idx[i]] = idx[v]; now[idx[i]].push_back(idx[v]); } } } }
- Gabow缩点模板,234MS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
#define M 20001 vector <int> edge[M];//原图的边 vector <int> now[M];//新图的边 int hash[M];//用来hash新图的重边 bool vis[M];//有无访问过该点 int pre[M],Index;//访问顺序 int idx[M];//点在新图的ID int ss[M],top;//放节点的栈 bool InStack[M];//元素是不是在栈里 int ss2[M],top2;//放最早遍历的节点(根) int n;//原图大小 int nn;//新图大小 void dfs(int u) { vis[u] = true; pre[u] = Index ++; InStack[u] = true; ss[++top] = u; ss2[++top2] = u; FF(i,SZ(edge[u])) { int v = edge[u][i]; if(!vis[v]) { dfs(v); } else if(InStack[v]) { while(pre[ss2[top2]] > pre[v]) { top2 --; } } } if(ss2[top2] == u) { top2 --; int v = -1; while(u != v) { v = ss[top--]; InStack[v] = false; idx[v] = nn; } now[nn++].clear(); } } void Gabow() { CC(vis,false); Index = 0; top = 0; top2 = 0; nn = 0; FF(i,n) { if(vis[i]) continue; dfs(i); } CC(hash,-1); FF(i,n) { FF(j,SZ(edge[i])) { int v = edge[i][j]; if(idx[i] != idx[v] && hash[idx[i]] != idx[v]) { hash[idx[i]] = idx[v]; now[idx[i]].push_back(idx[v]); } } } }
Trajan和Gabow其实可以省略vis和InStack数组,分别用pre和idx代替
省略后的终极模板 - 静态邻接表Trajan,125MS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
#define M 50001 struct Node{ int v,next; }edge[200001]; int now[M],init[M]; int pre[M],Index; int low[M],idx[M],hash[M]; int ss[M],top; int n , nn , len; void dfs(int u) { pre[u] = low[u] = Index ++; ss[++top] = u; for(int i = init[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if(pre[v] == -1) { dfs(v); checkmin(low[u],low[v]); } else if(idx[v] == -1) { checkmin(low[u],pre[v]); } } if(pre[u] == low[u]) { int v = -1; while(u != v) { v = ss[top--]; idx[v] = nn; } now[nn++] = -1; } } void Trajan() { memset(pre,-1,sizeof(int)*(n+1)); memset(idx,-1,sizeof(int)*(n+1)); Index = top = nn = 0; FF(i,n) if(pre[i] == -1) { dfs(i); } memset(hash,-1,sizeof(int)*(n+1)); FF(u,n) { for(int i = init[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if(idx[u] != idx[v] && hash[idx[u]] != idx[v]) { hash[idx[u]] = idx[v]; edge[len].next = now[ idx[u] ]; edge[len].v = idx[v]; now[ idx[u] ] = len++; } } } }
- vector Gabow,218MS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
#define M 20001 vector <int> edge[M];//原图的边 vector <int> now[M];//新图的边 int hash[M];//用来hash新图的重边 bool vis[M];//有无访问过该点 int pre[M],Index;//访问顺序 int idx[M];//点在新图的ID int ss[M],top;//放节点的栈 bool InStack[M];//元素是不是在栈里 int ss2[M],top2;//放最早遍历的节点(根) int n;//原图大小 int nn;//新图大小 void dfs(int u) { pre[u] = Index ++; ss[++top] = u; ss2[++top2] = u; FF(i,SZ(edge[u])) { int v = edge[u][i]; if(pre[v] == -1) { dfs(v); } else if(idx[v] == -1) {//表示还在栈里边 while(pre[ss2[top2]] > pre[v]) { top2 --; } } } if(ss2[top2] == u) { top2 --; int v = -1; while(u != v) { v = ss[top--]; idx[v] = nn; } now[nn++].clear(); } } void Gabow() { CC(idx,-1);//代替vis,因为每遍历一个节点都会 CC(pre,-1); Index = top = top2 = nn = 0; FF(i,n) { if(pre[i] != -1) continue; dfs(i); } CC(hash,-1); FF(u,n) FF(i,SZ(edge[u])) { int v = edge[u][i]; if(idx[u] != idx[v] && hash[idx[u]] != idx[v]) { hash[idx[u]] = idx[v]; now[idx[u]].push_back(idx[v]); } } }
最后改用静态表,直接彪到125MS,感叹vector好慢啊。。。
下边是用到缩点的题,没有特意去找,做到后更新
- FZU1719 Spy Network
缩点后的价值取环内最小的价值,然后把入度为0的间谍全部买下来是去省钱的方式,如果不能买就输出NO
- hdu3072 Intelligence System
缩点后找[u->v]价值最小的cost记录下来,然后答案就是cost[v]总和(除根节点)