先来%一下Robert Tarjan前辈

%%%%%%%%%%%%%%%%%%
然后是热情感谢下列并不止这些大佬的博客:
图连通性(一):Tarjan算法求解有向图强连通分量
图连通性(二):Tarjan算法求解割点/桥/双连通分量/LCA
初探tarjan算法(求强连通分量)
关于Tarjan算法求点双连通分量
图的割点、桥与双连通分支
感谢有各位大佬的博客帮助我理解和学习,接下来就是进入正题。
关于tarjan,之前我写过一个是求lca的随笔,而找lca只是它一个小小的功能,它还有很多其他功能,具体是什么,我就根据自己的学习进程,一步步来自我回忆一下。
第一个是有向图求强连通分量。
让我们来引进百度
有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
迷宫城堡
HDU - 1269

1 #include<cstdio>
2 #include<algorithm>
3 using namespace std;
4 const int N=1e4+11,M=1e5+11;
5 struct Side{
6 int v,ne;
7 }S[M];
8 bool ins[N],in[N],out[N];
9 int n,sn,dn,tn,head[N],dfn[N],low[N],sta[N];
10 int cn,col[N];
11 void init(){
12 sn=dn=tn=cn=0;
13 for(int i=0;i<=n;i++){
14 head[i]=-1;
15 dfn[i]=0;
16 }
17 }
18 void add(int u,int v){
19 S[sn].v=v;
20 S[sn].ne=head[u];
21 head[u]=sn++;
22 }
23 void tarjan(int u){
24 ins[u]=true;
25 sta[++tn]=u;
26 dfn[u]=low[u]=++dn;
27 for(int i=head[u],v;~i;i=S[i].ne){
28 v=S[i].v;
29 if(!dfn[v]){
30 tarjan(v);
31 low[u]=min(low[u],low[v]);
32 }else if(ins[v]) low[u]=min(low[u],dfn[v]);
33 }
34 if(dfn[u]==low[u]){
35 col[u]=++cn;
36 ins[u]=false;
37 while(sta[tn]!=u){
38 col[sta[tn]]=cn;
39 ins[sta[tn--]]=false;
40 }
41 tn--;
42 }
43 }
44 int main(){
45 int m,u,v;
46 while(scanf("%d%d",&n,&m)&&(n||m)){
47 init();
48 while(m--){
49 scanf("%d%d",&u,&v);
50 add(u,v);
51 }
52 for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
53 if(cn==1) puts("Yes");
54 else puts("No");
55 }
56 return 0;
57 }
P2863牛的舞会The Cow Prom
题意:两只牛通过绳子连起来,一个牛能不能跳圆舞就是看顺着绳子的方向,能不能回到它。一只牛是跳不了圆舞的,问能跳圆舞的牛的组合有多少组。
就求点数大于的强连通分量个数。


1 #include<cstdio>
2 #include<algorithm>
3 using namespace std;
4 const int N=1e4+118,M=5e4+118;
5 struct Side{
6 int v,ne;
7 }S[M];
8 bool ins[N];
9 int sn,dn,tn,ans,head[N],dfn[N],low[N],sta[N];
10 void init(int n){
11 sn=dn=tn=ans=0;
12 sta[0]=-1;
13 for(int i=0;i<=n;i++){
14 dfn[i]=0;
15 head[i]=-1;
16 }
17 }
18 void add(int u,int v){
19 S[sn].v=v;
20 S[sn].ne=head[u];
21 head[u]=sn++;
22 }
23 void tarjan(int u){
24 ins[u]=true;
25 sta[++tn]=u;
26 dfn[u]=low[u]=++dn;
27 for(int i=head[u],v;~i;i=S[i].ne){
28 v=S[i].v;
29 if(!dfn[v]){
30 tarjan(v);
31 low[u]=min(low[u],low[v]);
32 }else if(ins[v]) low[u]=min(low[u],dfn[v]);
33 }
34 if(dfn[u]==low[u]){
35 int num=1;
36 ins[u]=false;
37 while(sta[tn]!=u){
38 num++;
39 ins[sta[tn--]]=false;
40 }
41 if(num>1) ans++;
42 tn--;
43 }
44 }
45 int main(){
46 int n,m,u,v;
47 while(~scanf("%d%d",&n,&m)){
48 init(n);
49 for(int i=0;i<m;i++){
50 scanf("%d%d",&u,&v);
51 add(u,v);
52 }
53 for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
54 printf("%d\n",ans);
55 }
56 return 0;
57 }
P1726 上白泽慧音
题意:求最大的强连通分量,多个的话输出字典序最小的。
一个节点只会属于一个连通块,直接记录强连通记录和最小的节点编号,然后把同一个强连通的节点染色一下,由小到大把属于答案那个强连通分量的节点编号输出。


1 #include<cstdio>
2 #include<algorithm>
3 using namespace std;
4 const int N=5e3+11,M=5e4+11;
5 struct Side{
6 int v,ne;
7 }S[M<<1];
8 bool ins[N];
9 int n,sn,tn,dn,head[N],dfn[N],low[N],sta[N];
10 int cn,ansc,col[N],size[N],minc[N];
11 void init(){
12 sn=dn=tn=cn=0;
13 ansc=0;
14 for(int i=0;i<=n;i++){
15 dfn[i]=0;
16 size[i]=0;
17 minc[i]=n+1;
18 head[i]=-1;
19 }
20 }
21 void add(int u,int v){
22 S[sn].v=v;
23 S[sn].ne=head[u];
24 head[u]=sn++;
25 }
26 void tarjan(int u){
27 ins[u]=true;
28 sta[++tn]=u;
29 dfn[u]=low[u]=++dn;
30 for(int i=head[u],v;~i;i=S[i].ne){
31 v=S[i].v;
32 if(!dfn[v]){
33 tarjan(v);
34 low[u]=min(low[u],low[v]);
35 }else if(ins[v]) low[u]=min(low[u],dfn[v]);
36 }
37 if(dfn[u]==low[u]){
38 col[u]=++cn;
39 ins[u]=false;
40 size[cn]=1;
41 minc[cn]=u;
42 while(sta[tn]!=u){
43 col[sta[tn]]=cn;
44 size[cn]++;
45 minc[cn]=min(minc[cn],sta[tn]);
46 ins[sta[tn--]]=false;
47 }
48 if(size[cn]>size[ansc]||(size[cn]==size[ansc]&&minc[cn]<minc[ansc])) ansc=cn;
49 tn--;
50 }
51 }
52 int main(){
53 int m,u,v,op;
54 while(~scanf("%d%d",&n,&m)){
55 init();
56 while(m--){
57 scanf("%d%d%d",&u,&v,&op);
58 add(u,v);
59 if(op==2) add(v,u);
60 }
61 for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
62 printf("%d\n",size[ansc]);
63 int kg=0;
64 for(int i=1;i<=n;i++) if(col[i]==ansc){
65 if(kg) printf(" ");
66 kg=1;
67 printf("%d",i);
68 }
69 printf("\n");
70 }
71 return 0;
72 }
Proving EquivalencesHDU - 2767
题意:a公式能推导b,b能推导c,c能推导a,说明3个等式的等价的,给出n个公式,和m个推导关系,问最少加多少个推导,使得属于公式等价。
其实就是要最少加多少条边使得整个图是强连通的,原图中是有环的,所以我们就用tarjan先把强连通缩点,然后的话,就是一个有向无环图了,那怎么使得我们要使得这个图是强连通的话,其实就是看入度为0和出度为0的点
因为要能到其他点和能被其他点到达,所以肯定要有出有入,那就是出度为0的可以向入度为0的点连一条边,剩下的可以任意连或者自环,答案也就是出度和入度为0的点中,点数更多的那个,还有就是只有一个点的是0,不是1.。。。


1 #include<cstdio>
2 #include<algorithm>
3 using namespace std;
4 const int N=2e4+11,M=5e4+11;
5 struct Side{
6 int v,ne;
7 }S[M];
8 bool ins[N],in[N],out[N];
9 int n,sn,dn,tn,head[N],dfn[N],low[N],sta[N];
10 int cn,col[N];
11 void init(){
12 sn=dn=tn=cn=0;
13 for(int i=0;i<=n;i++){
14 head[i]=-1;
15 dfn[i]=0;
16 }
17 }
18 void add(int u,int v){
19 S[sn].v=v;
20 S[sn].ne=head[u];
21 head[u]=sn++;
22 }
23 void tarjan(int u){
24 ins[u]=true;
25 sta[++tn]=u;
26 dfn[u]=low[u]=++dn;
27 for(int i=head[u],v;~i;i=S[i].ne){
28 v=S[i].v;
29 if(!dfn[v]){
30 tarjan(v);
31 low[u]=min(low[u],low[v]);
32 }else if(ins[v]) low[u]=min(low[u],dfn[v]);
33 }
34 if(dfn[u]==low[u]){
35 col[u]=++cn;
36 ins[u]=false;
37 while(sta[tn]!=u){
38 col[sta[tn]]=cn;
39 ins[sta[tn--]]=false;
40 }
41 tn--;
42 }
43 }
44 int solve(){
45 if(cn==1) return 0;
46 for(int i=1;i<=cn;i++) in[i]=out[i]=false;
47 int ci,cj;
48 for(int i=1;i<=n;i++){
49 ci=col[i];
50 for(int j=head[i];~j;j=S[j].ne){
51 cj=col[S[j].v];
52 if(ci!=cj){
53 out[ci]=true;
54 in[cj]=true;
55 }
56 }
57 }
58 int noi=0,noo=0;
59 for(int i=1;i<=cn;i++){
60 if(!in[i]) noi++;
61 if(!out[i]) noo++;
62 }
63 return max(noi,noo);
64 }
65 int main(){
66 int t,m,u,v;
67 scanf("%d",&t);
68 while(t--){
69 scanf("%d%d",&n,&m);
70 init();
71 while(m--){
72 scanf("%d%d",&u,&v);
73 add(u,v);
74 }
75 for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
76 printf("%d\n",solve());
77 }
78 return 0;
79 }
Summer HolidayHDU - 1827
题意:听说lcy帮大家预定了新马泰7日游,Wiskey真是高兴的夜不能寐啊,他想着得快点把这消息告诉大家,虽然他手上有所有人的联系方式,但是一个一个联系过去实在太耗时间和电话费了。他知道其他人也有一些别人的联系方式,这样他可以通知其他人,再让其他人帮忙通知一下别人。你能帮Wiskey计算出至少要通知多少人,至少得花多少电话费就能让所有人都被通知到吗?
就先把强连通缩点,这个点的贡献就是整个强连通里权值最小的那个,然后的话就是看入度为0的点的贡献,因为他们没有人通知,肯定只能由wis通知。


1 //把每个环缩点,统计每个环的最小贡献
2 #include<cstdio>
3 #include<algorithm>
4 using namespace std;
5 const int N=1e3+11,M=2e3+11;
6 struct Side{
7 int v,ne;
8 }S[M];
9 bool ins[N],in[N];
10 int n,sn,dn,tn,head[N],dfn[N],low[N],sta[N];
11 int cn,col[N],minc[N],cost[N];
12 void init(){
13 sn=dn=tn=cn=0;
14 for(int i=0;i<=n;i++){
15 head[i]=-1;
16 dfn[i]=0;
17 }
18 }
19 void add(int u,int v){
20 S[sn].v=v;
21 S[sn].ne=head[u];
22 head[u]=sn++;
23 }
24 void tarjan(int u){
25 ins[u]=true;
26 sta[++tn]=u;
27 dfn[u]=low[u]=++dn;
28 for(int i=head[u],v;~i;i=S[i].ne){
29 v=S[i].v;
30 if(!dfn[v]){
31 tarjan(v);
32 low[u]=min(low[u],low[v]);
33 }else if(ins[v]) low[u]=min(low[u],dfn[v]);
34 }
35 if(dfn[u]==low[u]){
36 col[u]=++cn;
37 ins[u]=false;
38 minc[cn]=cost[u];
39 while(sta[tn]!=u){
40 col[sta[tn]]=cn;
41 minc[cn]=min(minc[cn],cost[sta[tn]]);
42 ins[sta[tn--]]=false;
43 }
44 tn--;
45 }
46 }
47 void solve(){
48 for(int i=1;i<=cn;i++) in[i]=false;
49 int ci,cj;
50 for(int i=1;i<=n;i++){
51 ci=col[i];
52 for(int j=head[i];~j;j=S[j].ne){
53 cj=col[S[j].v];
54 if(ci!=cj) in[cj]=true;
55 }
56 }
57 int ans1=0,ans2=0;
58 for(int i=1;i<=cn;i++) if(!in[i]) ans1++,ans2+=minc[i];
59 printf("%d %d\n",ans1,ans2);
60 }
61 int main(){
62 int m,u,v;
63 while(~scanf("%d%d",&n,&m)){
64 init();
65 for(int i=1;i<=n;i++) scanf("%d",&cost[i]);
66 while(m--){
67 scanf("%d%d",&u,&v);
68 add(u,v);
69 }
70 for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
71 solve();
72 }
73 return 0;
74 }
Intelligence SystemHDU - 3072
题意:要把一个情报通知给所有人,彼此能通知到的人属于同一分支,一个人通知跟他同一分支的人不需要花费,否则就有一个花费,求通知所有人的最小花费。
也先强连通缩点,然后的话就是更新一下通知到这个点的最小花费。


1 #include<cstdio>
2 #include<algorithm>
3 using namespace std;
4 const int N=5e4+11,M=1e5+11,inf=1e9+7;
5 struct Side{
6 int v,ne,w;
7 }S[M];
8 bool ins[N];
9 int n,sn,dn,tn,head[N],dfn[N],low[N],sta[N];
10 int cn,col[N],minc[N];
11 void init(){
12 sn=dn=tn=cn=0;
13 for(int i=0;i<=n;i++){
14 head[i]=-1;
15 dfn[i]=0;
16 }
17 }
18 void add(int u,int v,int w){
19 S[sn].w=w;
20 S[sn].v=v;
21 S[sn].ne=head[u];
22 head[u]=sn++;
23 }
24 void tarjan(int u){
25 ins[u]=true;
26 sta[++tn]=u;
27 dfn[u]=low[u]=++dn;
28 for(int i=head[u],v;~i;i=S[i].ne){
29 v=S[i].v;
30 if(!dfn[v]){
31 tarjan(v);
32 low[u]=min(low[u],low[v]);
33 }else if(ins[v]) low[u]=min(low[u],dfn[v]);
34 }
35 if(dfn[u]==low[u]){
36 col[u]=++cn;
37 ins[u]=false;
38 while(sta[tn]!=u){
39 col[sta[tn]]=cn;
40 ins[sta[tn--]]=false;
41 }
42 tn--;
43 }
44 }
45 void solve(){
46 for(int i=1;i<=cn;i++) minc[i]=inf;
47 int ci,cj;
48 for(int i=0;i<n;i++){
49 ci=col[i];
50 for(int j=head[i];~j;j=S[j].ne){
51 cj=col[S[j].v];
52 if(ci!=cj) minc[cj]=min(minc[cj],S[j].w);
53 }
54 }
55 long long ans=0;
56 for(int i=1;i<=cn;i++) if(minc[i]!=inf) ans+=minc[i];
57 printf("%lld\n",ans);
58 }
59 int main(){
60 int m,u,v,w;
61 while(~scanf("%d%d",&n,&m)){
62 init();
63 while(m--){
64 scanf("%d%d%d",&u,&v,&w);
65 add(u,v,w);
66 }
67 for(int i=0;i<n;i++) if(!dfn[i]) tarjan(i);
68 solve();
69 }
70 return 0;
71 }
P3387 【模板】缩点
题意:给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
先缩点,然后重新建图就是一个有向无环图了,找到入度为0的点,由它开始dp每个点,更新走到这个点的最大权值,然后最后取一下答案最大值


1 #include<cstdio>
2 #include<vector>
3 #include<algorithm>
4 using namespace std;
5 const int N=1e4+11,M=1e5+11;
6 struct Side{
7 int v,ne;
8 }S[M];
9 bool ins[N];
10 int n,sn,dn,tn,head[N],dfn[N],low[N],sta[N];
11 int cn,col[N],val[N],ww[N],in[N],dp[N];
12 vector<int> vv[N];
13 void init(){
14 sn=dn=tn=cn=0;
15 for(int i=0;i<=n;i++){
16 dfn[i]=0;
17 val[i]=0;
18 head[i]=-1;
19 }
20 }
21 void add(int u,int v){
22 S[sn].v=v;
23 S[sn].ne=head[u];
24 head[u]=sn++;
25 }
26 void tarjan(int u){
27 ins[u]=true;
28 sta[++tn]=u;
29 dfn[u]=low[u]=++dn;
30 for(int i=head[u],v;~i;i=S[i].ne){
31 v=S[i].v;
32 if(!dfn[v]){
33 tarjan(v);
34 low[u]=min(low[u],low[v]);
35 }else if(ins[v]) low[u]=min(low[u],dfn[v]);
36 }
37 if(dfn[u]==low[u]){
38 col[u]=++cn;
39 val[cn]=ww[u];
40 ins[u]=false;
41 while(sta[tn]!=u){
42 col[sta[tn]]=cn;
43 val[cn]+=ww[sta[tn]];
44 ins[sta[tn--]]=false;
45 }
46 tn--;
47 }
48 }
49 void dfs(int u){
50 int v,usize=vv[u].size();
51 for(int i=0;i<usize;i++){
52 v=vv[u][i];
53 dp[v]=max(dp[v],dp[u]+val[u]);
54 dfs(v);
55 }
56 }
57 int solve(){
58 for(int i=1;i<=cn;i++){
59 in[i]=0;
60 dp[i]=0;
61 vv[i].clear();
62 }
63 int cu,cv,ans=0;
64 for(int i=1;i<=n;i++){
65 cu=col[i];
66 for(int j=head[i];~j;j=S[j].ne){
67 cv=col[S[j].v];
68 if(cu!=cv){
69 in[cv]++;
70 vv[cu].push_back(cv);
71 }
72 }
73 }
74 for(int i=1;i<=cn;i++) if(!in[i]) dfs(i);
75 for(int i=1;i<=cn;i++) ans=max(ans,dp[i]+val[i]);
76 return ans;
77 }
78 int main(){
79 int m,u,v;
80 while(~scanf("%d%d",&n,&m)){
81 init();
82 for(int i=1;i<=n;i++) scanf("%d",&ww[i]);
83 while(m--){
84 scanf("%d%d",&u,&v);
85 add(u,v);
86 }
87 for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
88 printf("%d\n",solve());
89 }
90 return 0;
91 }
强连通的题找的比较多,所以多弄几题,可以看见板子题就是板子题,套就完事了。
然后就是在无向图中的了,割点,桥,点(边)双连通分量。
直接引用一下上面大佬博客的
1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点。
2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。
3.点连通度:最小割点集合中的顶点数。
4.割边(桥):删掉它之后,图必然会分裂为两个或两个以上的子图。
5.割边集合:如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。
6.边连通度:一个图的边连通度的定义为,最小割边集合中的边数。
7.缩点:把没有割边的连通子图缩为一个点,此时满足任意两点之间都有两条路径可达。
注:求块<>求缩点。缩点后变成一棵k个点k-1条割边连接成的树。而割点可以存在于多个块中。
8.双连通分量:分为点双连通和边双连通。它的标准定义为:点连通度大于1的图称为点双连通图,边连通度大于1的图称为边双连通图。通俗地讲,满足任意两点之间,能通过两条或两条以上没有任何重复边的路到达的图称为双连通图。无向图G的极大双连通子图称为双连通分量。
通俗解释就是。。没有啥通俗解释,上面的定义就是相应的意思。
那我们就是来一个个来说一下怎么求,首先说明,tarjan算法求得的东西是各个连通块的东西,所以要是要求的原图的东西的话,还需要判连通来判断一下。
然后我们就先来说一个割点怎么求。
首先跟求强连通分量差不多,我们还是得把没访问的点遍历一遍,然后像树的遍历一样去访问这个点以及它能走到的那些点,去更新low,但不需要把点压入栈中,也就是如果v是u的祖先,直接更新low,不用判断在不在栈里(因为压根没有)
然后当我们遍历完u的下一节点v,回溯到u时,如果low[v]>=dfn[u]的话,不就说明,v没法走到u的祖先。所以把u割去,必定分成多个连通块,所以u就是割点。
不过这里得判断u是不是根节点,也就是用来一开始遍历的那个节点,因为像1->2,2->3,3->1的话,用1来作为根节点,也有low[2]>=dfn[1],但1不是割点
那根节点是不是割点怎么判断呢,也就是看一下它能访问到的连通块有多少个,如果是大于等于两个的话,那它很明显也是割点了。
我最喜欢的做题实战环节。
P3388 【模板】割点(割顶)
题意:给出一个nn个点,mm条边的无向图,求图的割点。
割点模板题,原图不一定连通,要求的是各个连通块的割点。


1 #include<cstdio>
2 #include<vector>
3 using namespace std;
4 const int N=2e4+11;
5 bool isc[N];
6 int n,dn,dfn[N],low[N];
7 vector<int> vv[N];
8 int ans;
9 void init(int n){
10 dn=0;
11 for(int i=0;i<=n;i++){
12 dfn[i]=0;
13 isc[i]=false;
14 vv[i].clear();
15 }
16 }
17 void findc(int u,int fa){
18 dfn[u]=low[u]=++dn;
19 int v,vvs=vv[u].size(),son=0;
20 for(int i=0;i<vvs;i++){
21 v=vv[u][i];
22 if(!dfn[v]){
23 if(u==fa) son++;
24 findc(v,u);
25 low[u]=min(low[u],low[v]);
26 if(low[v]>=dfn[u]&&u!=fa) isc[u]=true;
27 }else low[u]=min(low[u],dfn[v]);
28 }
29 if(u==fa&&son>=2) isc[u]=true;
30 if(isc[u]) ans++;
31 }
32 int main(){
33 int n,m,u,v;
34 while(~scanf("%d%d",&n,&m)){
35 init(n);
36 while(m--){
37 scanf("%d%d",&u,&v);
38 vv[u].push_back(v);
39 vv[v].push_back(u);
40 }
41 ans=0;
42 for(int i=1;i<=n;i++) if(!dfn[i]) findc(i,i);
43 printf("%d\n",ans);
44 int kg=0;
45 for(int i=1;i<=n;i++) if(isc[i]){
46 if(kg) printf(" ");
47 kg=1;
48 printf("%d",i);
49 }
50 printf("\n");
51 }
52 return 0;
53 }
SPFPOJ - 1523
题意:如果一个节点如果它不可用了,将阻止至少一对可用节点能够在先前完全连接的网络上进行通信,那么它是SPF,求哪些节点是SPF,并且它故障后留下几个子网。
也是割点模板题,子网其实就是连通块,对于一个割点来说,它割掉之后产生的连通块就是能让它是割点的点个数,因为那些点就代表着它那个连通块。


#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1e3+11;
int dn,dfn[N],low[N],cut[N];
vector<int> vv[N];
void init(int n){
dn=0;
for(int i=0;i<=1000;i++){
cut[i]=0;
dfn[i]=0;
vv[i].clear();
}
}
void findc(int u,int fa){
dfn[u]=low[u]=++dn;
int v,son=0,vvs=vv[u].size();
for(int i=0;i<vvs;i++){
v=vv[u][i];
if(!dfn[v]){
if(u==fa) son++;
findc(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]) cut[u]++;
}else low[u]=min(low[u],dfn[v]);
}
if(u==fa) cut[u]=son-1;
}
int main(){
int u,v=0,t=1;
init(1000);
while(~scanf("%d",&u)){
if(u){
scanf("%d",&v);
vv[u].push_back(v);
vv[v].push_back(u);
}
else{
if(!v) break;
for(int i=1;i<=1000;i++) if(!dfn[i]) findc(i,i);
printf("Network #%d\n",t++);
int num=0;
for(int i=1;i<=1000;i++) if(cut[i]>0){
num++;
printf(" SPF node %d leaves %d subnets\n",i,cut[i]+1);
}
if(num==0) printf(" No SPF nodes\n");
printf("\n");
init(1000);
v=0;
}
}
return 0;
}
然后从割点也可以看出桥(割边)怎么求了,其他一样,不同的是后向边的话不是u才更新,然后判断一下low[v]>dfn[u],这样的话v连u都访问不到,说明u->v割去,必定分成多个连通块,所以u->v就是桥。
但其实这里面还有一个问题,这是建立在没有重边的情况下,有重边的话像1->2 1->2 明显1->2不是割边,但是用上面方法1->2是割边,所以有割边的话需要先进行预处理一下。
但也有不需要预处理的方法,就上面博客有提到,我直接引用一下
我们记录每条边的标号(一条无向边拆成的两条有向边标号相同),记录每个点的父亲到它的边的标号,如果边(u,v)是v的父亲边,就不能用dfn[u]更新low[v]。这样如果遍历完v的所有子节点后,发现low[v]=dfn[v],说明u的父亲边(u,v)为割边。
这样我们通过记录边的编号就很好判断了重边。
Caocao's BridgesHDU - 4738
题意:人妻操在海上弄了些小岛,彼此用桥连接,周瑜只有一个炸弹,要派人去把一个桥炸了,使得所有小岛分成几个连通块,桥上有守卫,派的人不能少于守卫数,问最少派多少人,或者根本不可能完成任务。
就是找权值最小的桥,但是要的是整个图的,所以如果一开始就不连通的话,就不需要派人了,这个没注意到wrong了几发,然后还有就是如果权值最小的是0,也得派一个人。


1 #include<cstdio>
2 #include<algorithm>
3 using namespace std;
4 const int N=1e3+11,M=1e6+11;
5 struct Side{
6 int v,ne,id;
7 }S[M<<1];
8 int ans;
9 int n,sn,dn,head[N],dfn[N],low[N],fp[N],fa[N],val[M];
10 void init(){
11 sn=dn=0;
12 for(int i=0;i<=n;i++){
13 fa[i]=i;
14 fp[i]=-1;
15 dfn[i]=0;
16 head[i]=-1;
17 }
18 }
19 void add(int u,int v,int id){
20 S[sn].id=id;
21 S[sn].v=v;
22 S[sn].ne=head[u];
23 head[u]=sn++;
24 }
25 int find(int x){
26 return fa[x]==x ? x : fa[x]=find(fa[x]);
27 }
28 void bing(int x,int y){
29 int fx=find(x),fy=find(y);
30 if(fx!=fy) fa[fx]=fy;
31 }
32 void findq(int u){
33 dfn[u]=low[u]=++dn;
34 int v,id;
35 for(int i=head[u];~i;i=S[i].ne){
36 v=S[i].v;
37 id=S[i].id;
38 if(!dfn[v]){
39 fp[v]=id;
40 findq(v);
41 low[u]=min(low[u],low[v]);
42 }else if(id!=fp[u]) low[u]=min(low[u],dfn[v]);
43 }
44 if(dfn[u]==low[u]&&fp[u]!=-1){
45 if(ans==-1) ans=val[fp[u]];
46 else ans=min(ans,val[fp[u]]);
47 }
48 }
49 int main(){
50 int m,u,v,w;
51 while(~scanf("%d%d",&n,&m)&&(n||m)){
52 init();
53 while(m--){
54 scanf("%d%d%d",&u,&v,&w);
55 val[m]=w;
56 add(u,v,m);
57 add(v,u,m);
58 bing(u,v);
59 }
60 ans=-1;
61 int flag=1,beg=0;
62 for(int i=1;i<=n;i++) if(fa[i]==i){
63 if(beg){
64 flag=0;
65 break;
66 }
67 beg=i;
68 }
69 if(!flag){
70 printf("0\n");
71 continue;
72 }
73 findq(beg);
74 if(!ans) ans++;
75 printf("%d\n",ans);
76 }
77 return 0;
78 }
桥的话直接求的比较少,一般是用在求边双连通上。
求点双连通的话,网上的写法太多了,而且不一致,我最后选择了我认为比较对的写法。
对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储当前双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。
但其中,DFS(u)<=Low(v),说明u是一个割点的话,跟上面的求割点的说法冲突了,这里我也是满疑惑的,等着可爱的你们给我解惑。
Knights of the Round TablePOJ - 2942
题意:圆桌会议,每个骑士周围坐着两个骑士,彼此有仇的骑士不能坐在周围,开会的骑士的人数得是大于等于3的奇数,问怎么也开不了会的骑士有几个。
这题嘛,就首先,。。。点开题解。。。我是真没想到怎么是点双连通。
然后看完题解之后,我们领悟了,首先每个骑士周围都坐着两个人,然后是围成一个圈,其实这就是个点双连通,每个骑士彼此可达,所以题目也就是求多少个其实不能出现在双连通的奇圈里。(奇圈就是点数是奇数的圈)
这里是有个结论的,如果一个双连通分量内的某些顶点在一个奇圈中(即双连通分量含有奇圈),那么这个双连通分量的其他顶点也在某个奇圈中证明就不证明了,反正我不会,一句话,记住就好。
然后怎么判断有没有奇圈呢,一个图是二分图当且仅当图中不存在奇圈,所以反过来直接判断这个双连通是不是二分图就好了,判断二分图的话就是染色法。


1 #include<cstdio>
2 #include<vector>
3 #include<stack>
4 #include<algorithm>
5 using namespace std;
6 const int N=1e3+11,M=1e6+11;
7 struct Side{
8 int u,v,ne;
9 Side(){}
10 Side(int u,int v):u(u),v(v){}
11 }S[M<<1];
12 bool hate[N][N],book[N],expel[N];
13 int n,sn,dn,bn,head[N],dfn[N],low[N],col[N],bin[N];
14 stack<Side> sta;
15 vector<int> bb[N];
16 void init(){
17 sn=dn=bn=0;
18 while(sta.size()) sta.pop();
19 for(int i=0;i<=n;i++){
20 dfn[i]=0;
21 bin[i]=0;
22 head[i]=-1;
23 expel[i]=true;
24 for(int j=0;j<=n;j++) hate[i][j]=false;
25 }
26 }
27 void add(int u,int v){
28 S[sn].v=v;
29 S[sn].ne=head[u];
30 head[u]=sn++;
31 }
32 bool oddc(int u,int cc){
33 col[u]=cc;
34 for(int i=head[u],v;~i;i=S[i].ne){
35 v=S[i].v;
36 if(!book[v]) continue;
37 if(!col[v]&&oddc(v,-cc)) return true;
38 if(col[v]==cc) return true;
39 }
40 return false;
41 }
42 void pdc(int u,int fa){
43 dfn[u]=low[u]=++dn;
44 for(int i=head[u],v;~i;i=S[i].ne){
45 v=S[i].v;
46 if(!dfn[v]){
47 sta.push(Side(u,v));
48 pdc(v,u);
49 low[u]=min(low[u],low[v]);
50 if(low[v]>=dfn[u]){
51 bb[++bn].clear();
52 for(int i=0;i<=n;i++){
53 col[i]=0;
54 book[i]=false;
55 }
56 while(!sta.empty()){
57 int su=sta.top().u,sv=sta.top().v;
58 sta.pop();
59 if(bin[su]!=bn){
60 bin[su]=bn;
61 book[su]=true;
62 bb[bn].push_back(su);
63 }
64 if(bin[sv]!=bn){
65 bin[sv]=bn;
66 book[sv]=true;
67 bb[bn].push_back(sv);
68 }
69 if(su==u&&sv==v) break;
70 }
71 if(oddc(u,1)){
72 int bbs=bb[bn].size();
73 if(bbs<3) continue;
74 for(int i=0;i<bbs;i++) expel[bb[bn][i]]=false;
75 }
76 }
77 }else if(v!=fa){
78 low[u]=min(low[u],dfn[v]);
79 if(dfn[u]>dfn[v]) sta.push(Side(u,v));
80 }
81 }
82 }
83 int main(){
84 int m,u,v;
85 while(~scanf("%d%d",&n,&m)&&(n||m)){
86 init();
87 while(m--){
88 scanf("%d%d",&u,&v);
89 hate[u][v]=hate[v][u]=true;
90 }
91 for(int i=1;i<=n;i++)
92 for(int j=i+1;j<=n;j++) if(!hate[i][j]){
93 add(i,j);
94 add(j,i);
95 }
96 for(int i=1;i<=n;i++) if(!dfn[i]) pdc(i,i);
97 int ans=0;
98 for(int i=1;i<=n;i++) if(expel[i]) ans++;
99 printf("%d\n",ans);
100 }
101 return 0;
102 }
啊啊啊, 从12点写到3点还没写完,溜溜球了,睡觉先,剩下的明天搞。