强连通缩点
其法有三: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]] &gt; 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]] &gt; 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好慢啊。。。

下边是用到缩点的题,没有特意去找,做到后更新

  1. FZU1719 Spy Network

    缩点后的价值取环内最小的价值,然后把入度为0的间谍全部买下来是去省钱的方式,如果不能买就输出NO

  2. hdu3072 Intelligence System

    缩点后找[u->v]价值最小的cost记录下来,然后答案就是cost[v]总和(除根节点)