先来%一下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 - 12691 #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点还没写完,溜溜球了,睡觉先,剩下的明天搞。