写在前面:
Tarjan全家桶
Tarjan的代码除了Tarjan求LCA外,长得都一样,分清楚细微差别就好了
关于代码,我是复制了第一个板子后删改写出来的,长得极为相似,很多地方只有两三行不一样,建议双屏逐行对比一下看看具体哪里不一样
需要特别注意的是,在无向图中,边双连通分量需要用边标记法,而点双连通分量需要用点标记法,切勿弄混
非原创内容标明出处
目录 |
有向图
强连通: 在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通。可知至少要有三个点 强连通图: 如果有向图G的每两个点都强连通,称G是一个强连通图 强连通分量: 有向图G的极大强连通子图,称为强连通分量 ——bia度百科 例子:
网上用烂的图 1,4强连通 图G'(包含点1,4,5,2和仅在这四点之间相互连接的5条边)是强连通图 而图G(包含所有点,所有边)不是强连通图 上述图G'是图G的一个(这里也是唯一一个)极大强连通子图(它不能再拓展,再拓展就不是强连通图),即强连通分量 |
思路:
搜索 → 优化的搜索(保证每个点必然且仅仅搜索1次,且记录每个点搜索的顺序) → 引入栈来辅助
具体来说,是从某个点开始,带着两个信息minTime(思路上,栈中的元素是按照minTime分组的)和vTime(每个点唯一一次被搜索时的次序)去拓展
如果某个点在某个组(即某个强连通分量)里,那么它的minTime(原来等于它本身的vTime)必然会被更新(更新成该强连通分量里点的最小的minTime,有可能是它自己)
在每一次搜索后,都去比对刚刚搜过的点的minTime和vTime一不一样,如果一样,那么就把它和它后面的所有点(如果有的话)全部清出栈,而每一次清出来的一组点(它们的minTime相同)就是一个强连通分量
代码如下:
C++:
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 const int MAXN=100010,MAXM=100010;
6 int n,m;
7
8 //------Tarjan------
9 int vTime[MAXN],minTime[MAXN],Vtime;
10 //------Tarjan------
11
12 //------手写栈------
13 int index,stackk[MAXM];
14 bool vis[MAXN];
15 //------手写栈------
16
17 //------存图------
18 int heads[MAXN],numedge=1,u,v;
19 struct EDGE{
20 int next,to;
21 }edge[MAXM];
22 inline void adde(int u,int v){
23 ++numedge;
24 edge[numedge].next=heads[u];
25 edge[numedge].to=v;
26 heads[u]=numedge;
27 }
28 //------存图------
29
30 void tarjan(int x)
31 {
32 vTime[x]=minTime[x]=++Vtime;
33 stackk[++index]=x;vis[x]=1;//维护栈来找强连通分量
34 for(int i=heads[x];i!=0;i=edge[i].next)
35 {
36 if(!vTime[edge[i].to])//能拓展的点仍未访问过
37 {
38 tarjan(edge[i].to);
39 minTime[x]=min(minTime[x],minTime[edge[i].to]);
40 }
41 else if(vis[edge[i].to])//能拓展的点访问过,且在栈里面
42 {
43 minTime[x]=min(minTime[x],vTime[edge[i].to]);
44 }
45 }
46
47 /*每搜索一个点后*/
48 if(minTime[x]==vTime[x]){
49 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
50 //上面这行用来查看栈里有什么
51 do{
52 printf("%d ",stackk[index]);
53 vis[stackk[index]]=0;
54 index--;
55 }while(stackk[index+1]!=x);
56 printf("\n");
57 }
58 return;
59 }
60
61 int main(int argc,char* argv[], char* enc[])
62 {
63 scanf("%d%d",&n,&m);
64 for(int i=1;i<=m;++i){
65 scanf("%d%d",&u,&v);
66 adde(u,v);
67 }
68
69 for(int i=1;i<=n;++i)
70 if(!vTime[i]) tarjan(i);//如果没有被搜索过,就搜索这个点
71
72 return 0;
73 }
Java:
1 import java.util.Scanner;
2
3 class Pony
4 {
5 static int MAXN=100010,MAXM=100010;
6 static int n,m;
7
8 //------Tarjan------
9 static int Vtime;
10 static int[] vTime=new int[MAXN],minTime=new int[MAXN];
11 //------Tarjan------
12
13 //------手写栈------
14 static int index;
15 static int[] stackk=new int[MAXM];
16 static boolean[] vis=new boolean[MAXN];
17 //------手写栈------
18
19 //------存图------
20 static int numedge=1,u,v;
21 static int[] heads=new int[MAXN],edgeNext=new int[MAXM],edgeTo=new int[MAXM];
22 static void adde(int u,int v){
23 ++numedge;
24 edgeNext[numedge]=heads[u];
25 edgeTo[numedge]=v;
26 heads[u]=numedge;
27 }
28 //------存图------
29
30 static int min(int a,int b){return a<b? a:b;}
31
32 static void tarjan(int x)
33 {
34 vTime[x]=minTime[x]=++Vtime;
35 stackk[++index]=x;vis[x]=true;//维护栈来找强连通分量
36 for(int i=heads[x];i!=0;i=edgeNext[i])
37 {
38 if(vTime[edgeTo[i]]==0)//能拓展的点仍未访问过
39 {
40 tarjan(edgeTo[i]);
41 minTime[x]=min(minTime[x],minTime[edgeTo[i]]);
42 }
43 else if(vis[edgeTo[i]])//能拓展的点访问过,且在栈里面
44 {
45 minTime[x]=min(minTime[x],vTime[edgeTo[i]]);
46 }
47 }
48
49 /*每搜索一个点后*/
50 if(minTime[x]==vTime[x]){
51 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
52 //上面这行用来查看栈里有什么
53 do{
54 System.out.printf("%d ",stackk[index]);
55 vis[stackk[index]]=false;
56 --index;
57 }while(stackk[index+1]!=x);
58 System.out.printf("\n");
59 }
60 return;
61 }
62
63 public static void main(String[] args) throws Exception
64 {
65 Scanner cin=new Scanner(System.in);
66
67 n=cin.nextInt();
68 m=cin.nextInt();
69 for(int i=1;i<=m;++i){
70 u=cin.nextInt();
71 v=cin.nextInt();
72 adde(u,v);
73 }
74
75 for(int i=1;i<=n;++i)
76 if(vTime[i]==0) tarjan(i);//如果没有被搜索过,就搜索这个点
77 }
78 }
测试数据:
6 8 1 4 |
无向图
边双连通: 无向图G中,对于一个连通图,如果任意两点至少存在两条“边不重复”的路径(点可以重复),则说图是边双连通的 边双连通分量: 边双连通的极大子图称为边双连通分量 |
思路:
Tarjan求强通分量 → 转化为无向图(记录从哪条边搜过来的防止在两点之间重复搜索)
具体来说,就是套用有向图求强连通分量的思路,加上在无向边中防止在两点间来回搜索的技巧(用边标记,别用父子节点标记,否则无法判断两点之间多条边的情况)
我们使用的是边标记法,而不是父子节点标记法,判断两点之间的多条边就很轻松了
代码如下:
C++:
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 const int MAXN=100010,MAXM=100010;
6 int n,m;
7
8 //------Tarjan------
9 int vTime[MAXN],minTime[MAXN],Vtime;
10 //------Tarjan------
11
12 //------手写栈------
13 int index,stackk[MAXM];
14 bool vis[MAXN];
15 //------手写栈------
16
17 //------存图------
18 int heads[MAXN],numedge=1,u,v;
19 struct EDGE{
20 int next,to;
21 }edge[MAXM<<1];
22 inline void adde(int u,int v){
23 ++numedge;//格外注意这里,第一条边的编号是2
24 edge[numedge].next=heads[u];
25 edge[numedge].to=v;
26 heads[u]=numedge;
27 }
28 //------存图------
29
30 void tarjan(int x,int sEdge)//sEdge表示搜到这点的边
31 {
32 vTime[x]=minTime[x]=++Vtime;
33 stackk[++index]=x;vis[x]=1;//维护栈来找边双连通分量
34 for(int i=heads[x];i!=0;i=edge[i].next)
35 {
36 if(!vTime[edge[i].to])//即下一个点仍未访问过
37 {
38 tarjan(edge[i].to,i);
39 minTime[x]=min(minTime[x],minTime[edge[i].to]);//更新,同下
40 }
41 else if(i!=(sEdge^1))//下一个点访问过,且不是同一条边,即出现双联通分量,另外注意位运算符的优先级比较低,需要括号
42 {
43 minTime[x]=min(minTime[x],vTime[edge[i].to]);//更新,同上
44 }
45 }
46
47 /*每搜索一个点后*/
48 if(minTime[x]==vTime[x]){
49 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
50 //上面这行用来查看栈里有什么
51 do{
52 printf("%d ",stackk[index]);
53 vis[stackk[index]]=0;
54 --index;
55 }while(stackk[index+1]!=x);
56 printf("\n");
57 }
58 return;
59 }
60
61 int main(int argc,char* argv[], char* enc[])
62 {
63 scanf("%d%d",&n,&m);
64 for(int i=1;i<=m;++i){
65 scanf("%d%d",&u,&v);
66 adde(u,v),adde(v,u);
67 }
68
69 for(int i=1;i<=n;++i)
70 if(!vTime[i]) tarjan(i,0);
71
72 return 0;
73 }
Java:
1 import java.util.Scanner;
2
3 class Pony
4 {
5 static int MAXN=100010,MAXM=100010;
6 static int n,m;
7
8 //------Tarjan------
9 static int Vtime;
10 static int[] vTime=new int[MAXN],minTime=new int[MAXN];
11 //------Tarjan------
12
13 //------手写栈------
14 static int index;
15 static int[] stackk=new int[MAXM];
16 static boolean[] vis=new boolean[MAXN];
17 //------手写栈------
18
19 //------存图------
20 static int numedge=1,u,v;//格外注意这里,第一条边的编号是2
21 static int[] heads=new int[MAXN],edgeNext=new int[MAXM],edgeTo=new int[MAXM];
22 static void adde(int u,int v){
23 ++numedge;
24 edgeNext[numedge]=heads[u];
25 edgeTo[numedge]=v;
26 heads[u]=numedge;
27 }
28 //------存图------
29
30 static int min(int a,int b){return a<b? a:b;}
31
32 static void tarjan(int x,int sEdge)//sEdge表示搜到这点的边
33 {
34 vTime[x]=minTime[x]=++Vtime;
35 stackk[++index]=x;vis[x]=true;//维护栈来找边双连通分量
36 for(int i=heads[x];i!=0;i=edgeNext[i])
37 {
38 if(vTime[edgeTo[i]]==0)//即下一个点仍未访问过
39 {
40 tarjan(edgeTo[i],i);
41 minTime[x]=min(minTime[x],minTime[edgeTo[i]]);//更新,同下
42 }
43 else if(i!=(sEdge^1))//下一个点访问过,且不是同一条边,即出现双联通分量,另外注意位运算符的优先级比较低,需要括号
44 {
45 minTime[x]=min(minTime[x],vTime[edgeTo[i]]);//更新,同上
46 }
47 }
48
49 /*每搜索一个点后*/
50 if(minTime[x]==vTime[x]){
51 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
52 //上面这行用来查看栈里有什么
53 do{
54 System.out.printf("%d ",stackk[index]);
55 vis[stackk[index]]=false;
56 --index;
57 }while(stackk[index+1]!=x);
58 System.out.printf("\n");
59 }
60 return;
61 }
62
63 public static void main(String[] args) throws Exception
64 {
65 Scanner cin=new Scanner(System.in);
66
67 n=cin.nextInt();
68 m=cin.nextInt();
69 for(int i=1;i<=m;++i){
70 u=cin.nextInt();
71 v=cin.nextInt();
72 adde(u,v);adde(v,u);
73 }
74
75 for(int i=1;i<=n;++i)
76 if(vTime[i]==0) tarjan(i,0);
77 }
78 }
测试数据:
5 5
5 6 2 3 2 1 3 1 4 1 4 1 4 5 |
无向图
桥(割边): 边双连通分量G去掉某一条边后就不是边双连通分量,那么这条边就是桥(割边) 或者说,边双连通分量G'去掉某一条边后边连通块的个数增加,那么这条边就是桥(割边) ——http://www.cnblogs.com/zwfymqz/p/8480552.html 例子:
图G'(包含点2,1,3和三点之间的3条无向边)是边双连通的,且是边双连通分量 去掉边1-4或边4-5后,图G'就不是边双连通分量,则这两条边都是桥(割边) |
思路:
Tarjan求边双连通分量 → 比较搜索的相邻两点之间的minTime和vTime判断是不是桥
具体来说,就是套用求边双连通分量的思路,加上判定桥(割边)的技巧
每次搜索一个点Ti的回溯过程中,如果它连接的点Ti+1的minTime>Ti本身的vTime,那么说明它之后的点没有其他路径回到这个点,这两个点之间的这一条边是他们连通的唯一路径,就是桥(割边)
特殊地,如果两条点之间有两条及以上的边(重边),或者两条以上的路径,那么这两条边都不是桥(割边)
而我们使用的是边标记法(防止在两点之间重复搜索的标记法),而不是父子节点标记法,判断两点之间的多条边就很轻松了
代码如下:
C++:
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 const int MAXN=100010,MAXM=100010;
6 int n,m;
7
8 //------Tarjan------
9 int vTime[MAXN],minTime[MAXN],Vtime;
10 //------Tarjan------
11
12 //------存图------
13 int heads[MAXN],numedge=1,u,v;
14 struct EDGE{
15 int next,to;
16 }edge[MAXM<<1];
17 inline void adde(int u,int v){
18 ++numedge;//格外注意这里,第一条边的编号是2
19 edge[numedge].next=heads[u];
20 edge[numedge].to=v;
21 heads[u]=numedge;
22 }
23 //------存图------
24
25 void tarjan(int x,int sEdge)//sEdge表示搜到这点的边
26 {
27 vTime[x]=minTime[x]=++Vtime;
28 for(int i=heads[x];i!=0;i=edge[i].next)
29 {
30 if(!vTime[edge[i].to])//即下一个点仍未访问过
31 {
32 tarjan(edge[i].to,i);
33
34 minTime[x]=min(minTime[x],minTime[edge[i].to]);//更新,同下
35 if(minTime[edge[i].to]>vTime[x])
36 printf("桥%d-%d\n",edge[i].to,x);
37 //minTime[edge[i].to]>vTime[x],即后面的点无法回到x点之前
38 }
39 else if(i!=(sEdge^1))//下一个点访问过,且不是同一条边,即出现双联通分量,另外注意位运算符的优先级比较低,需要括号
40 {
41 minTime[x]=min(minTime[x],vTime[edge[i].to]);//更新,同上
42 }
43 }
44 return;
45 }
46
47 int main(int argc,char* argv[], char* enc[])
48 {
49 scanf("%d%d",&n,&m);
50 for(int i=1;i<=m;++i){
51 scanf("%d%d",&u,&v);
52 adde(u,v),adde(v,u);
53 }
54
55 for(int i=1;i<=n;++i)
56 if(!vTime[i]) tarjan(i,0);
57
58 return 0;
59 }
Java:
1 import java.util.Scanner;
2
3 class Pony
4 {
5 static int MAXN=100010,MAXM=100010;
6 static int n,m;
7
8 //------Tarjan------
9 static int Vtime;
10 static int[] vTime=new int[MAXN],minTime=new int[MAXN];
11 //------Tarjan------
12
13 //------存图------
14 static int numedge=1,u,v;//格外注意这里,第一条边的编号是2
15 static int[] heads=new int[MAXN],edgeNext=new int[MAXM],edgeTo=new int[MAXM];
16 static void adde(int u,int v){
17 ++numedge;
18 edgeNext[numedge]=heads[u];
19 edgeTo[numedge]=v;
20 heads[u]=numedge;
21 }
22 //------存图------
23
24 static int min(int a,int b){return a<b? a:b;}
25
26 static void tarjan(int x,int sEdge)//sEdge表示搜到这点的边
27 {
28 vTime[x]=minTime[x]=++Vtime;
29 for(int i=heads[x];i!=0;i=edgeNext[i])
30 {
31 if(vTime[edgeTo[i]]==0)//即下一个点仍未访问过
32 {
33 tarjan(edgeTo[i],i);
34
35 minTime[x]=min(minTime[x],minTime[edgeTo[i]]);//更新,同下
36 if(minTime[edgeTo[i]]>vTime[x])
37 System.out.printf("桥%d-%d\n",edgeTo[i],x);
38 //minTime[edge[i].to]>vTime[x],即后面的点无法回到x点之前
39 }
40 else if(i!=(sEdge^1))//下一个点访问过,且不是同一条边,即出现双联通分量,另外注意位运算符的优先级比较低,需要括号
41 {
42 minTime[x]=min(minTime[x],vTime[edgeTo[i]]);//更新,同上
43 }
44 }
45 return;
46 }
47
48 public static void main(String[] args) throws Exception
49 {
50 Scanner cin=new Scanner(System.in);
51
52 n=cin.nextInt();
53 m=cin.nextInt();
54 for(int i=1;i<=m;++i){
55 u=cin.nextInt();
56 v=cin.nextInt();
57 adde(u,v);adde(v,u);
58 }
59
60 for(int i=1;i<=n;++i)
61 if(vTime[i]==0) tarjan(i,0);
62 }
63 }
测试数据:
5 5
5 6 2 3 2 1 3 1 4 1 4 1 4 5 |
无向图
点双连通: 无向图G中,对于一个连通图,如果任意两点至少存在两条“点不重复”的路径(边可以重复),则说图是点双连通的 点双连通分量: 点双连通的极大子图称为点双连通分量 |
思路:
Tarjan求强通分量 → 转化为无向图(记录从哪个点搜过来的防止在两点之间重复搜索)
具体来说,就是套用有向图求强连通分量的思路,加上在无向边中防止在两点间来回搜索的技巧(用父子节点标记,别用边标记)
我们使用的是父子节点标记法,而不是边标记法,这样得到的才是点双连通分量
代码如下:
C++:
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 const int MAXN=100010,MAXM=100010;
6 int n,m;
7
8 //------Tarjan------
9 int vTime[MAXN],minTime[MAXN],Vtime;
10 //------Tarjan------
11
12 //------手写栈------
13 int index,stackk[MAXM];
14 bool vis[MAXN];
15 //------手写栈------
16
17 //------存图------
18 int heads[MAXN],numedge=1,u,v;
19 struct EDGE{
20 int next,to;
21 }edge[MAXM<<1];
22 inline void adde(int u,int v){
23 ++numedge;
24 edge[numedge].next=heads[u];
25 edge[numedge].to=v;
26 heads[u]=numedge;
27 }
28 //------存图------
29
30 void tarjan(int x,int fa)//fa表示搜到这点的父节点
31 {
32 vTime[x]=minTime[x]=++Vtime;
33 stackk[++index]=x;vis[x]=1;//维护栈来找点双连通分量
34 for(int i=heads[x];i!=0;i=edge[i].next)
35 {
36 if(!vTime[edge[i].to])//即仍未访问过
37 {
38 tarjan(edge[i].to,x);
39 minTime[x]=min(minTime[x],minTime[edge[i].to]);
40 }
41 else if(edge[i].to!=fa)
42 {
43 minTime[x]=min(minTime[x],vTime[edge[i].to]);
44 }
45 }
46
47 /*每搜索一个点后*/
48 if(minTime[x]==vTime[x]){
49 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
50 //上面这行用来查看栈里有什么
51 do{
52 printf("%d ",stackk[index]);
53 vis[stackk[index]]=0;
54 --index;
55 }while(stackk[index+1]!=x);
56 printf("\n");
57 }
58 return;
59 }
60
61 int main(int argc,char* argv[], char* enc[])
62 {
63 scanf("%d%d",&n,&m);
64 for(int i=1;i<=m;++i){
65 scanf("%d%d",&u,&v);
66 adde(u,v),adde(v,u);
67 }
68
69 for(int i=1;i<=n;++i)
70 if(!vTime[i]) tarjan(i,i);
71
72 return 0;
73 }
Java:
1 import java.util.Scanner;
2
3 class Pony
4 {
5 static int MAXN=100010,MAXM=100010;
6 static int n,m;
7
8 //------Tarjan------
9 static int Vtime;
10 static int[] vTime=new int[MAXN],minTime=new int[MAXN];
11 //------Tarjan------
12
13 //------手写栈------
14 static int index;
15 static int[] stackk=new int[MAXM];
16 static boolean[] vis=new boolean[MAXN];
17 //------手写栈------
18
19 //------存图------
20 static int numedge=1,u,v;
21 static int[] heads=new int[MAXN],edgeNext=new int[MAXM],edgeTo=new int[MAXM];
22 static void adde(int u,int v){
23 ++numedge;
24 edgeNext[numedge]=heads[u];
25 edgeTo[numedge]=v;
26 heads[u]=numedge;
27 }
28 //------存图------
29
30 static int min(int a,int b){return a<b? a:b;}
31
32 static void tarjan(int x,int fa)//fa表示搜到这点的父节点
33 {
34 vTime[x]=minTime[x]=++Vtime;
35 stackk[++index]=x;vis[x]=true;//维护栈来找点双连通分量
36 for(int i=heads[x];i!=0;i=edgeNext[i])
37 {
38 if(vTime[edgeTo[i]]==0)//即未访问过
39 {
40 tarjan(edgeTo[i],x);
41 minTime[x]=min(minTime[x],minTime[edgeTo[i]]);
42 }
43 else if(edgeTo[i]!=fa)
44 {
45 minTime[x]=min(minTime[x],vTime[edgeTo[i]]);
46 }
47 }
48
49 /*每搜索一个点后*/
50 if(minTime[x]==vTime[x]){
51 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
52 //上面这行用来查看栈里有什么
53 do{
54 System.out.printf("%d ",stackk[index]);
55 vis[stackk[index]]=false;
56 --index;
57 }while(stackk[index+1]!=x);
58 System.out.printf("\n");
59 }
60 return;
61 }
62
63 public static void main(String[] args) throws Exception
64 {
65 Scanner cin=new Scanner(System.in);
66
67 n=cin.nextInt();
68 m=cin.nextInt();
69 for(int i=1;i<=m;++i){
70 u=cin.nextInt();
71 v=cin.nextInt();
72 adde(u,v);adde(v,u);
73 }
74
75 for(int i=1;i<=n;++i)
76 if(vTime[i]==0) tarjan(i,i);
77 }
78 }
测试数据:
5 5
5 6 2 3 2 1 3 1 4 1 4 1 4 5 |
无向图
割点: 无向图G去掉某一个点后剩余的点就不能每一个相互到达,那么这个点就是割点 例子:
图G'(包含点2,1,3和三点之间的3条无向边)是点双连通的,且是点双连通分量 去掉点1或4后,整个图G中剩余的点不能每一个相互到达,那么这两个点就是割点 |
思路:
Tarjan求点双连通分量 → 比较搜索的相邻两点之间的minTime和vTime判断是不是割点
具体来说,就是套用求点双连通分量的思路,加上判定割点的技巧
因为每一次搜索的根结点比较特殊,它的fa就是它自己,不能使用和其子节点一样的方法搜索,所以单独拎出来,即:
①当前节点u不是本次搜索开始的根节点的时候:
在u之后遍历的点,向上翻,最多到u,那么u就是割点
如果能翻到u的前面,那么说明有环,去掉u之后,整个图G中剩余的点每一个都还能相互到达
②当前节点u是本次搜索开始的根节点的时候:
计算其子树数量,如果有2棵即以上的子树,u就是割点
如果去掉这个点,这两棵子树就不能互相到达
代码如下:
C++:
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 const int MAXN=100010,MAXM=100010;
6 int n,m;
7
8 //------Tarjan------
9 int vTime[MAXN],minTime[MAXN],Vtime;
10 //------Tarjan------
11
12 //------割点需要------
13 bool iscut[MAXN];
14 //可能会重复输出一个割点,那么就记录一下
15 //------割点需要------
16
17 //------存图------
18 int heads[MAXN],numedge=1,u,v;
19 struct EDGE{
20 int next,to;
21 }edge[MAXM<<1];
22 inline void adde(int u,int v){
23 ++numedge;
24 edge[numedge].next=heads[u];
25 edge[numedge].to=v;
26 heads[u]=numedge;
27 }
28 //------存图------
29
30 void tarjan(int x,int fa)//fa表示搜到这点的父节点
31 {
32 vTime[x]=minTime[x]=++Vtime;
33 int child=0;//记录根节点的孩子数
34 for(int i=heads[x];i!=0;i=edge[i].next)
35 {
36 if(!vTime[edge[i].to])//即仍未访问过
37 {
38 tarjan(edge[i].to,x);
39 minTime[x]=min(minTime[x],minTime[edge[i].to]);
40
41 if(x==fa) ++child;
42 else if(minTime[edge[i].to]>=vTime[x]) iscut[x]=1;
43 //(minTime[edge[i].to]>=vTime[x])即后面的点无法回到x点之前
44 }
45 else if(edge[i].to!=fa)
46 {
47 minTime[x]=min(minTime[x],vTime[edge[i].to]);
48 }
49 }
50 if(x==fa && child>=2) iscut[x]=1;
51 return;
52 }
53
54 int main(int argc,char* argv[], char* enc[])
55 {
56 scanf("%d%d",&n,&m);
57 for(int i=1;i<=m;++i){
58 scanf("%d%d",&u,&v);
59 adde(u,v),adde(v,u);
60 }
61
62 for(int i=1;i<=n;++i)
63 if(!vTime[i]) tarjan(i,i);
64
65 for(int i=1;i<=n;++i)
66 if(iscut[i]) printf("[%d]\n",i);
67
68 return 0;
69 }
Java:
1 import java.util.Scanner;
2
3 class Pony
4 {
5 static int MAXN=100010,MAXM=100010;
6 static int n,m;
7
8 //------Tarjan------
9 static int Vtime;
10 static int[] vTime=new int[MAXN],minTime=new int[MAXN];
11 //------Tarjan------
12
13 //------割点需要------
14 static boolean[] iscut=new boolean[MAXN];
15 //可能会重复输出一个割点,那么就记录一下
16 //------割点需要------
17
18 //------存图------
19 static int numedge=1,u,v;
20 static int[] heads=new int[MAXN],edgeNext=new int[MAXM],edgeTo=new int[MAXM];
21 static void adde(int u,int v){
22 ++numedge;
23 edgeNext[numedge]=heads[u];
24 edgeTo[numedge]=v;
25 heads[u]=numedge;
26 }
27 //------存图------
28
29 static int min(int a,int b){return a<b? a:b;}
30
31 static void tarjan(int x,int fa)//fa表示搜到这点的父节点
32 {
33 vTime[x]=minTime[x]=++Vtime;
34 int child=0;//记录根节点的孩子数
35 for(int i=heads[x];i!=0;i=edgeNext[i])
36 {
37 if(vTime[edgeTo[i]]==0)//即未访问过
38 {
39 tarjan(edgeTo[i],x);
40 minTime[x]=min(minTime[x],minTime[edgeTo[i]]);
41
42 if(x==fa) ++child;
43 else if(minTime[edgeTo[i]]>=vTime[x]) iscut[x]=true;
44 //(minTime[edge[i].to]>=vTime[x])即后面的点无法回到x点之前
45 }
46 else if(edgeTo[i]!=fa)
47 {
48 minTime[x]=min(minTime[x],vTime[edgeTo[i]]);
49 }
50 }
51 if(x==fa && child>=2) iscut[x]=true;
52 return;
53 }
54
55 public static void main(String[] args) throws Exception
56 {
57 Scanner cin=new Scanner(System.in);
58
59 n=cin.nextInt();
60 m=cin.nextInt();
61 for(int i=1;i<=m;++i){
62 u=cin.nextInt();
63 v=cin.nextInt();
64 adde(u,v);adde(v,u);
65 }
66
67 for(int i=1;i<=n;++i)
68 if(vTime[i]==0) tarjan(i,i);
69
70 for(int i=1;i<=n;++i)
71 if(iscut[i]) System.out.printf("[%d]\n",i);
72 }
73 }
测试数据:
5 5
5 6 2 3 2 1 3 1 4 1 4 1 4 5 |
有向图/无向图
缩点/染色,即消除图中的环,建立了一个新图
消除了环之后,不管是什么样的暴力就都很方便了(某些题目甚至直接缩点+大爆搜能过)
有向图:
Tarjan求强连通分量 → 在执行时顺便加上把强连通分量染色
具体来说,在将一组强连通分量清除出栈时保存一下每个点所在的是哪一个色块以及这个色块的大小(当然还可以搞一下色块出度,色块权值什么的)
在运行完Tarjan搜索后,遍历一遍所有边,可以看看每个色块有没有出度,顺便也可以把色块连出去
为了方便起见,在存图时不仅保存每条边的to,也保存每条边的from
代码如下:
C++:
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 const int MAXN=100010,MAXM=100010;
6 int n,m;
7
8 //------Tarjan------
9 int vTime[MAXN],minTime[MAXN],Vtime;
10 //------Tarjan------
11
12 //------缩点需要------
13 int colournum,inColour[MAXN],colourSize[MAXN],colourValue[MAXN],colourTo[MAXN];
14 bool outDegree[MAXN];
15 //colournum:集合(色块)的编号
16 //inClolur:u是属于哪个集合(色块)中的
17 //colourSize:集合(色块)所含有的元素个数
18 //colourValue:每个集合(色块)的权值
19 //colourTo:每个集合(色块)连接的下一个色块
20 //outDegree:每个集合(色块)的出度(要么是1要么是0)
21 //------缩点需要------
22
23 //------手写栈------
24 int index,stackk[MAXM];
25 bool vis[MAXN];
26 //------手写栈------
27
28 //------存图------
29 int heads[MAXN],numedge=1,u,v;
30 int value[MAXN];//点的权值
31 struct EDGE{
32 int next,from,to;
33 }edge[MAXM];
34 inline void adde(int u,int v){
35 ++numedge;
36 edge[numedge].next=heads[u];
37 edge[numedge].from=u;
38 edge[numedge].to=v;
39 heads[u]=numedge;
40 }
41 //------存图------
42
43 void tarjan(int x)
44 {
45 vTime[x]=minTime[x]=++Vtime;
46 stackk[++index]=x;vis[x]=1;//维护栈来找强连通分量
47 for(int i=heads[x];i!=0;i=edge[i].next)
48 {
49 if(!vTime[edge[i].to])//能拓展的点仍未访问过
50 {
51 tarjan(edge[i].to);
52 minTime[x]=min(minTime[x],minTime[edge[i].to]);
53 }
54 else if(vis[edge[i].to])//能拓展的点访问过,且在栈里面
55 {
56 minTime[x]=min(minTime[x],vTime[edge[i].to]);
57 }
58 }
59
60 /*每搜索一个点后*/
61 if(minTime[x]==vTime[x]){
62 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
63 //上面这行用来查看栈里有什么
64 ++colournum;
65 do{
66 //printf("%d ",stackk[index]);
67 vis[stackk[index]]=0;
68
69 inColour[stackk[index]]=colournum;
70 ++colourSize[colournum];
71 colourValue[colournum]+=value[stackk[index]];
72
73 --index;
74 }while(stackk[index+1]!=x);
75 //printf("\n");
76 }
77 return;
78 }
79
80 int main(int argc,char* argv[], char* enc[])
81 {
82 scanf("%d%d",&n,&m);
83 for(int i=1;i<=n;++i) scanf("%d",&value[i]);
84 for(int i=1;i<=m;++i){
85 scanf("%d%d",&u,&v);
86 adde(u,v);
87 }
88
89 for(int i=1;i<=n;++i)
90 if(!vTime[i]) tarjan(i);
91
92 for(int i=2;i<=numedge;++i)//枚举每条边,看看是否有两个色块之间相连,因为是有向图,所以必然有一个色块有出度(如果有就必然是1)
93 if(inColour[edge[i].from]!=inColour[edge[i].to]){
94 colourTo[inColour[edge[i].from]]=inColour[edge[i].to];
95 outDegree[inColour[edge[i].from]]=1;
96 }
97
98 for(int i=1;i<=colournum;++i){
99 printf("now_colcur[%d] size[%d] value[%d]",i,colourSize[i],colourValue[i]);
100 if(outDegree[i]) printf(" to colour %d\n",colourTo[i]);
101 else printf(" no out_degree\n");
102 }
103
104 return 0;
105 }
Java:
1 import java.util.Scanner;
2
3 class Pony
4 {
5 static int MAXN=100010,MAXM=100010;
6 static int n,m;
7
8 //------Tarjan------
9 static int Vtime;
10 static int[] vTime=new int[MAXN],minTime=new int[MAXN];
11 //------Tarjan------
12
13 //------缩点需要------
14 static int colournum;
15 static int[] inColour=new int[MAXN],colourSize=new int[MAXN];
16 static int[] colourValue=new int[MAXN],colourTo=new int[MAXN];
17 static boolean[] outDegree=new boolean[MAXN];
18 //colournum:集合(色块)的编号
19 //inClolur:u是属于哪个集合(色块)中的
20 //colourSize:集合(色块)所含有的元素个数
21 //colourValue:每个集合(色块)的权值
22 //colourTo:每个集合(色块)连接的下一个色块
23 //outDegree:每个集合(色块)的出度(要么是1要么是0)
24 //------缩点需要------
25
26 //------手写栈------
27 static int index;
28 static int[] stackk=new int[MAXM];
29 static boolean[] vis=new boolean[MAXN];
30 //------手写栈------
31
32 //------存图------
33 static int numedge=1,u,v;
34 static int[] value=new int[MAXN];//点的权值
35 static int[] heads=new int[MAXN];
36 static int[] edgeNext=new int[MAXM],edgeFrom=new int[MAXM],edgeTo=new int[MAXM];
37 static void adde(int u,int v){
38 ++numedge;
39 edgeNext[numedge]=heads[u];
40 edgeFrom[numedge]=u;
41 edgeTo[numedge]=v;
42 heads[u]=numedge;
43 }
44 //------存图------
45
46 static int min(int a,int b){return a<b? a:b;}
47
48 static void tarjan(int x)
49 {
50 vTime[x]=minTime[x]=++Vtime;
51 stackk[++index]=x;vis[x]=true;//维护栈来找强连通分量
52 for(int i=heads[x];i!=0;i=edgeNext[i])
53 {
54 if(vTime[edgeTo[i]]==0)//能拓展的点仍未访问过
55 {
56 tarjan(edgeTo[i]);
57 minTime[x]=min(minTime[x],minTime[edgeTo[i]]);
58 }
59 else if(vis[edgeTo[i]])//能拓展的点访问过,且在栈里面
60 {
61 minTime[x]=min(minTime[x],vTime[edgeTo[i]]);
62 }
63 }
64
65 /*每搜索一个点后*/
66 if(minTime[x]==vTime[x]){
67 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
68 //上面这行用来查看栈里有什么
69 ++colournum;
70 do{
71 //System.out.printf("%d ",stackk[index]);
72 vis[stackk[index]]=false;
73
74 inColour[stackk[index]]=colournum;
75 ++colourSize[colournum];
76 colourValue[colournum]+=value[stackk[index]];
77
78 --index;
79 }while(stackk[index+1]!=x);
80 //System.out.printf("\n");
81 }
82 return;
83 }
84
85 public static void main(String[] args) throws Exception
86 {
87 Scanner cin=new Scanner(System.in);
88
89 n=cin.nextInt();
90 m=cin.nextInt();
91 for(int i=1;i<=n;++i) value[i]=cin.nextInt();
92 for(int i=1;i<=m;++i){
93 u=cin.nextInt();
94 v=cin.nextInt();
95 adde(u,v);
96 }
97
98 for(int i=1;i<=n;++i)
99 if(vTime[i]==0) tarjan(i);
100
101 for(int i=2;i<=numedge;++i)//枚举每条边,看看是否有两个色块之间相连,因为是有向图,所以必然有一个色块有出度(如果有就必然是1)
102 if(inColour[edgeFrom[i]]!=inColour[edgeTo[i]]){
103 colourTo[inColour[edgeFrom[i]]]=inColour[edgeTo[i]];
104 outDegree[inColour[edgeFrom[i]]]=true;
105 }
106
107 for(int i=1;i<=colournum;++i){
108 System.out.printf("now_colcur[%d] size[%d] value[%d]",i,colourSize[i],colourValue[i]);
109 if(outDegree[i]) System.out.printf(" to colour %d\n",colourTo[i]);
110 else System.out.printf(" no out_degree\n");
111 }
112 }
113 }
测试数据:
黑色为点编号,红色为权值,绿色为新点的色块号 7 7 |
无向图:
Tarjan求点双连通分量 → 在执行时顺便加上把点双连通分量染色
大体同上,但由于是无向图,建立的新的图上点的连线就有一点麻烦了,这里就不演示连接新点了(有向无环图保证了有出度就是1,很方便存图)
代码如下:
C++:
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 const int MAXN=100010,MAXM=100010;
6 int n,m;
7
8 //------Tarjan------
9 int vTime[MAXN],minTime[MAXN],Vtime;
10 //------Tarjan------
11
12 //------缩点需要------
13 int colournum,inColour[MAXN],colourSize[MAXN],colourValue[MAXN];
14 //colournum:集合(色块)的编号
15 //inClolur:u是属于哪个集合(色块)中的
16 //colourSize:集合(色块)所含有的元素个数
17 //colourValue:每个集合(色块)的权值
18 //------缩点需要------
19
20 //------手写栈------
21 int index,stackk[MAXM];
22 bool vis[MAXN];
23 //------手写栈------
24
25 //------存图------
26 int heads[MAXN],numedge=1,u,v;
27 int value[MAXN];//点的权值
28 struct EDGE{
29 int next,to;
30 }edge[MAXM];
31 inline void adde(int u,int v){
32 ++numedge;
33 edge[numedge].next=heads[u];
34 edge[numedge].to=v;
35 heads[u]=numedge;
36 }
37 //------存图------
38
39 void tarjan(int x,int fa)
40 {
41 vTime[x]=minTime[x]=++Vtime;
42 stackk[++index]=x;vis[x]=1;//维护栈来找点双连通分量
43 for(int i=heads[x];i!=0;i=edge[i].next)
44 {
45 if(!vTime[edge[i].to])//能拓展的点仍未访问过
46 {
47 tarjan(edge[i].to,x);
48 minTime[x]=min(minTime[x],minTime[edge[i].to]);
49 }
50 else if(edge[i].to!=fa)
51 {
52 minTime[x]=min(minTime[x],vTime[edge[i].to]);
53 }
54 }
55
56 /*每搜索一个点后*/
57 if(minTime[x]==vTime[x]){
58 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
59 //上面这行用来查看栈里有什么
60 ++colournum;
61 do{
62 //printf("%d ",stackk[index]);
63 vis[stackk[index]]=0;
64
65 inColour[stackk[index]]=colournum;
66 ++colourSize[colournum];
67 colourValue[colournum]+=value[stackk[index]];
68
69 --index;
70 }while(stackk[index+1]!=x);
71 //printf("\n");
72 }
73 return;
74 }
75
76 int main(int argc,char* argv[], char* enc[])
77 {
78 scanf("%d%d",&n,&m);
79 for(int i=1;i<=n;++i) scanf("%d",&value[i]);
80 for(int i=1;i<=m;++i){
81 scanf("%d%d",&u,&v);
82 adde(u,v);
83 adde(v,u);
84 }
85
86 for(int i=1;i<=n;++i)
87 if(!vTime[i]) tarjan(i,i);
88
89 for(int i=1;i<=colournum;++i)
90 printf("now_colcur[%d] size[%d] value[%d]\n",i,colourSize[i],colourValue[i]);
91
92 return 0;
93 }
Java:
1 import java.util.Scanner;
2
3 class Pony
4 {
5 static int MAXN=100010,MAXM=100010;
6 static int n,m;
7
8 //------Tarjan------
9 static int Vtime;
10 static int[] vTime=new int[MAXN],minTime=new int[MAXN];
11 //------Tarjan------
12
13 //------缩点需要------
14 static int colournum;
15 static int[] inColour=new int[MAXN],colourSize=new int[MAXN];
16 static int[] colourValue=new int[MAXN];
17 //colournum:集合(色块)的编号
18 //inClolur:u是属于哪个集合(色块)中的
19 //colourSize:集合(色块)所含有的元素个数
20 //colourValue:每个集合(色块)的权值
21 //------缩点需要------
22
23 //------手写栈------
24 static int index;
25 static int[] stackk=new int[MAXM];
26 static boolean[] vis=new boolean[MAXN];
27 //------手写栈------
28
29 //------存图------
30 static int numedge=1,u,v;
31 static int[] value=new int[MAXN];//点的权值
32 static int[] heads=new int[MAXN];
33 static int[] edgeNext=new int[MAXM],edgeTo=new int[MAXM];
34 static void adde(int u,int v){
35 ++numedge;
36 edgeNext[numedge]=heads[u];
37 edgeTo[numedge]=v;
38 heads[u]=numedge;
39 }
40 //------存图------
41
42 static int min(int a,int b){return a<b? a:b;}
43
44 static void tarjan(int x,int fa)
45 {
46 vTime[x]=minTime[x]=++Vtime;
47 stackk[++index]=x;vis[x]=true;//维护栈来找点双连通分量
48 for(int i=heads[x];i!=0;i=edgeNext[i])
49 {
50 if(vTime[edgeTo[i]]==0)//能拓展的点仍未访问过
51 {
52 tarjan(edgeTo[i],x);
53 minTime[x]=min(minTime[x],minTime[edgeTo[i]]);
54 }
55 else if(edgeTo[i]!=fa)
56 {
57 minTime[x]=min(minTime[x],vTime[edgeTo[i]]);
58 }
59 }
60
61 /*每搜索一个点后*/
62 if(minTime[x]==vTime[x]){
63 //printf("【 ");for(int i=1;i<=index;i++)printf("%d ",stackk[i]);printf("】\n");
64 //上面这行用来查看栈里有什么
65 ++colournum;
66 do{
67 //System.out.printf("%d ",stackk[index]);
68 vis[stackk[index]]=false;
69
70 inColour[stackk[index]]=colournum;
71 ++colourSize[colournum];
72 colourValue[colournum]+=value[stackk[index]];
73
74 --index;
75 }while(stackk[index+1]!=x);
76 //System.out.printf("\n");
77 }
78 return;
79 }
80
81 public static void main(String[] args) throws Exception
82 {
83 Scanner cin=new Scanner(System.in);
84
85 n=cin.nextInt();
86 m=cin.nextInt();
87 for(int i=1;i<=n;++i) value[i]=cin.nextInt();
88 for(int i=1;i<=m;++i){
89 u=cin.nextInt();
90 v=cin.nextInt();
91 adde(u,v);adde(v,u);
92 }
93
94 for(int i=1;i<=n;++i)
95 if(vTime[i]==0) tarjan(i,i);
96
97 for(int i=1;i<=colournum;++i)
98 System.out.printf("now_colcur[%d] size[%d] value[%d]\n",i,colourSize[i],colourValue[i]);
99 }
100 }
测试数据:
黑色为点编号,红色为权值,绿色为新点的色块号
7 8 |
有向图
骨骼清奇的算法,和上面的模板不一样
是个离线算法,比较无力
必须保证处理的边严格按照父节点指向子节点,不然还要转化一下
在知道所有的要查询的点和所有点的父子关系后,进行一遍搜索,得出答案
具体步骤:
1.任选一个点为根节点,从根节点开始搜索
2.遍历点u所有子节点v,并标记这些子节点v已被访问过
3.若是v还有子节点,返回2,否则下一步
4.合并v到u上
5.寻找与当前点u有询问关系的点v
6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a
——https://www.cnblogs.com/JVxie/p/4854719.html
代码如下:
C++:
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 const int MAXN=10010,MAXM=10010;
6 int father[MAXN],n,q;
7 bool visit[MAXN];
8 vector<int> query[10010];
9
10 int u,v,numedge=1,heads[MAXN];
11 struct EDGE{
12 int next,to;
13 }edge[MAXM];
14 inline void adde(int u,int v){
15 ++numedge;
16 edge[numedge].next=heads[u];
17 edge[numedge].to=v;
18 heads[u]=numedge;
19 }
20
21 int pony_Connect(int x){//用父子关系连接起来的路径,我们要求的是lca,不能用路径压缩
22 if(father[x]!=x) return pony_Connect(father[x]);
23 else return x;
24 }
25
26 void dfs(int x){
27 for(int i=heads[x];i!=0;i=edge[i].next)
28 {
29 dfs(edge[i].to);
30 father[edge[i].to]=x;
31 visit[edge[i].to]=1;
32 }
33 //一边搜,一边处理要查找的lca
34 for(vector<int>::iterator i=query[x].begin();i!=query[x].end();++i)
35 if(visit[*i]) printf("[%d] [%d] LCA : %d\n",*i,x,pony_Connect(*i));
36 }
37
38 int main(int argc,char* argv[], char* enc[])
39 {
40 scanf("%d%d",&n,&q);
41 for(int i=1;i<=n;++i) father[i]=i;
42 for(int i=1;i<=n-1;++i){
43 scanf("%d%d",&u,&v);
44 adde(u,v);
45 }
46 for(int i=1;i<=q;++i){
47 scanf("%d%d",&u,&v);
48 query[u].push_back(v);
49 query[v].push_back(u);
50 }
51
52 dfs(1);
53
54 return 0;
55 }
Java:
不存在
测试数据:
注意,实际上是有向图 这组数据有9个节点,4次查询 9 4 1 2 4 5 |
有向图
无向图存在割点
有向图存在支配点[◹]支配树
可以利用Tarjan算法求出支配点
来源:oschina
链接:https://my.oschina.net/u/4332712/blog/3670228