强连通分量初探 By cellur925
Posted nopartyfoucaodong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了强连通分量初探 By cellur925相关的知识,希望对你有一定的参考价值。
并不理解。但是毕竟也做了一些题,略微小结。
注:这里讨论的暂时是有向图的强联通分量。
先贴出模板。学长:我也不理解,但我可以叫你们怎么背代码。
1 #include<cstdio> 2 #include<algorithm> 3 #include<stack> 4 #define maxn 5 6 using namespace std; 7 int dfn[maxn],low[maxn] 8 9 void dfs(int p) 10 { 11 dfn[p]=low[p]=++dfs_clock; 12 s.push(p); 13 for(int i=head[p];i;i=edge[i].next) 14 { 15 int y=edge[i].to; 16 if(!dfn[y]) 17 { 18 dfs(y); 19 low[p]=min(low[p],low[y]); 20 } 21 else if(!scc[y]) low[p]=min(low[p],dfn[y]); 22 } 23 if(dfn[p]==low[p]) 24 { 25 scc_cnt++; 26 while(1) 27 { 28 int x=s.top(); 29 s.pop(); 30 scc[x]=scc_cnt; 31 if(x==p) break; 32 } 33 } 34 } 35 36 int main() 37 { 38 scanf("%d",&n); 39 for(int i=1;i<=m;i++) 40 //读入边信息 41 for(int i=1;i<=n;i++) 42 { 43 if(!dfn[i]) dfs(i); 44 tong[scc[i]]++; 45 //scc[i]->i点在哪个强连通分量 46 //tong[i]-> 第i个强连通分量大小 47 } 48 return 0; 49 }
一 缩点
一句话来说,就是求出有向图中的强联通分量后,把每个强联通分量用一个点代替,得到一个DAG(有向无环图)。
我们用一个新的邻接表来记录新的DAG上的边。
这个过程可以近似的理解为缩点。先放下求缩点的模板。
1 #include<cstdio> 2 #include<algorithm> 3 #include<stack> 4 #include<queue> 5 #include<cstring> 6 7 using namespace std; 8 //tarjan_need 9 int n,m,x,y,tot,qwq,ans,scc_cnt,dfs_clock,sum[10090],pv[10090],head[10090],head_DAG[10090],low[10090],dfn[10090],scc[10090]; 10 struct node{ 11 int next,to; 12 }edge[100090]; 13 struct nodee{ 14 int next,to; 15 }edge_DAG[100090]; 16 stack<int>s; 17 18 void add(int x,int y,int op) 19 { 20 if(op==1) 21 { 22 edge[++tot].to=y; 23 edge[tot].next=head[x]; 24 head[x]=tot; 25 } 26 else if(op==2) 27 { 28 edge_DAG[++qwq].to=y; 29 edge_DAG[qwq].next=head_DAG[x]; 30 head_DAG[x]=qwq; 31 } 32 } 33 34 void tarjan(int p) 35 { 36 dfn[p]=low[p]=++dfs_clock; 37 s.push(p); 38 for(int i=head[p];i;i=edge[i].next) 39 { 40 int y=edge[i].to; 41 if(!dfn[y]) 42 { 43 tarjan(y); 44 low[p]=min(low[p],low[y]); 45 } 46 else if(!scc[y]) low[p]=min(low[p],dfn[y]); 47 } 48 if(dfn[p]==low[p]) 49 { 50 scc_cnt++; 51 while(1) 52 { 53 int x=s.top(); 54 s.pop(); 55 scc[x]=scc_cnt; 56 if(x==p) break; 57 } 58 } 59 } 60 61 int main() 62 { 63 scanf("%d%d",&n,&m); 64 for(int i=1;i<=n;i++) scanf("%d",&pv[i]); 65 for(int i=1;i<=m;i++) 66 scanf("%d%d",&x,&y),add(x,y,1); 67 for(int i=1;i<=n;i++) 68 if(!dfn[i]) tarjan(i); 69 for(int x=1;x<=n;x++) 70 for(int i=head[x];i;i=edge[i].next) 71 { 72 int y=edge[i].to; 73 if(scc[x]!=scc[y]) 74 add(scc[x],scc[y],2); 75 } 76 return 0; 77 }
然鹅洛谷的模板题更深一步,求:
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
我们跑一遍tarjan进行缩点,得到一个新的DAG,在DAG上枚举起点,各跑一遍spfa求最长路,每跑完再枚举终点。注意这里是点带权而不是边带权,在进行强联通分量个数划分的时候,我们需要更新合并每个SCC的点权和(注意用新的数组保存!被这个地方卡了)。
1 #include<cstdio> 2 #include<algorithm> 3 #include<stack> 4 #include<queue> 5 #include<cstring> 6 7 using namespace std; 8 //tarjan_need 9 int n,m,x,y,tot,qwq,ans,scc_cnt,dfs_clock,sum[10090],pv[10090],head[10090],head_DAG[10090],low[10090],dfn[10090],scc[10090]; 10 struct node{ 11 int next,to; 12 }edge[100090]; 13 struct nodee{ 14 int next,to; 15 }edge_DAG[100090]; 16 stack<int>s; 17 //spfa_need 18 int dis[10090]; 19 bool vis[10090]; 20 21 void add(int x,int y,int op) 22 { 23 if(op==1) 24 { 25 edge[++tot].to=y; 26 edge[tot].next=head[x]; 27 head[x]=tot; 28 } 29 else if(op==2) 30 { 31 edge_DAG[++qwq].to=y; 32 edge_DAG[qwq].next=head_DAG[x]; 33 head_DAG[x]=qwq; 34 } 35 } 36 37 void tarjan(int p) 38 { 39 dfn[p]=low[p]=++dfs_clock; 40 s.push(p); 41 for(int i=head[p];i;i=edge[i].next) 42 { 43 int y=edge[i].to; 44 if(!dfn[y]) 45 { 46 tarjan(y); 47 low[p]=min(low[p],low[y]); 48 } 49 else if(!scc[y]) low[p]=min(low[p],dfn[y]); 50 } 51 if(dfn[p]==low[p]) 52 { 53 scc_cnt++; 54 while(1) 55 { 56 int x=s.top(); 57 s.pop(); 58 scc[x]=scc_cnt; 59 sum[scc_cnt]+=pv[x]; 60 if(x==p) break; 61 } 62 } 63 } 64 65 void spfa(int start) 66 { 67 memset(dis,0,sizeof(dis)); 68 memset(vis,0,sizeof(vis)); 69 queue<int>q; 70 q.push(start);dis[start]=sum[start];vis[start]=1; 71 while(!q.empty()) 72 { 73 int x=q.front();q.pop();vis[x]=0; 74 for(int i=head_DAG[x];i;i=edge_DAG[i].next) 75 { 76 int y=edge_DAG[i].to; 77 if(dis[y]<dis[x]+sum[y]) 78 { 79 dis[y]=dis[x]+sum[y]; 80 if(!vis[y]) 81 { 82 vis[y]=1; 83 q.push(y); 84 } 85 } 86 } 87 } 88 for(int i=1;i<=scc_cnt;i++) ans=max(ans,dis[i]); 89 } 90 91 int main() 92 { 93 scanf("%d%d",&n,&m); 94 for(int i=1;i<=n;i++) scanf("%d",&pv[i]); 95 for(int i=1;i<=m;i++) 96 scanf("%d%d",&x,&y),add(x,y,1); 97 for(int i=1;i<=n;i++) 98 if(!dfn[i]) tarjan(i); 99 for(int x=1;x<=n;x++) 100 for(int i=head[x];i;i=edge[i].next) 101 { 102 int y=edge[i].to; 103 if(scc[x]!=scc[y]) 104 add(scc[x],scc[y],2); 105 } 106 for(int i=1;i<=scc_cnt;i++) spfa(i); 107 printf("%d",ans); 108 return 0; 109 }
二、受欢迎的牛
题目描述
每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶
牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜
欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你
算出有多少头奶牛可以当明星。
题意可以再简化: 给定一个有向图,问有多少个顶点是由任何顶点出发都可达的。
分析:(抄lyc课件)
? 有向无环图中唯一出度为 0 的点,一定可以由任何点出发均可达
? 由于无环,所以从任何点出发往前走,必然终止于一个出度为0
的点
? 求出所有强连通分量,每个强连通分量缩成一点,形成一个有向
无环图DAG。
? DAG上面如果恰有一个出度为0的点,说明DAG所有的点可到达这
个点,这个点是原图中的强连通分量,该强连通分量的点数,就
是答案。
? DAG上面如果有不止一个出度为0的点,因为它们不能互相到达,
故原问题无解,答案为0
code
1 #include<cstdio> 2 #include<algorithm> 3 #include<stack> 4 5 using namespace std; 6 7 int n,m,tot,qwq,Chemist,cellur,dfs_clock,scc_cnt,qaq,qaqaq; 8 int head[10090],head_DAG[10090],dfn[10090],low[10090],scc[10090],du[10090],tong[10090]; 9 struct node{ 10 int to,next; 11 }edge[50090]; 12 stack<int>s; 13 14 void add(int x,int y,int op) 15 { 16 if(op==1) 17 { 18 edge[++tot].to=y; 19 edge[tot].next=head[x]; 20 head[x]=tot; 21 } 22 } 23 24 void dfs(int p) 25 { 26 dfn[p]=low[p]=++dfs_clock; 27 s.push(p); 28 for(int i=head[p];i;i=edge[i].next) 29 { 30 int y=edge[i].to; 31 if(!dfn[y]) 32 { 33 dfs(y); 34 low[p]=min(low[p],low[y]); 35 } 36 else if(!scc[y]) low[p]=min(low[p],dfn[y]); 37 } 38 if(dfn[p]==low[p]) 39 { 40 scc_cnt++; 41 while(1) 42 { 43 int x=s.top(); 44 s.pop(); 45 scc[x]=scc_cnt; 46 if(x==p) break; 47 } 48 } 49 } 50 51 int main() 52 { 53 scanf("%d%d",&n,&m); 54 for(int i=1;i<=m;i++) 55 scanf("%d%d",&Chemist,&cellur),add(Chemist,cellur,1); 56 for(int i=1;i<=n;i++) 57 { 58 if(!dfn[i]) dfs(i); 59 tong[scc[i]]++; 60 } 61 for(int x=1;x<=n;x++) 62 for(int i=head[x];i;i=edge[i].next) 63 { 64 int y=edge[i].to; 65 if(scc[x]!=scc[y]) du[scc[x]]++; 66 } 67 68 for(int i=1;i<=scc_cnt;i++) 69 if(du[i]==0) qaq++,qaqaq=i; 70 71 if(qaq==1) printf("%d",tong[qaqaq]); 72 else printf("0"); 73 return 0; 74 }
三、信息传递
题目描述
有 n 个同学(编号为 1 到 n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,
其中,编号为 ii 的同学的信息传递对象是编号为 Ti? 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
题意也是比较明白,求图中的最小环,我们可以用tarjan求(虽然有些小题大做的嫌疑)。记录各个强联通分量的大小,最后找最小的大于1的环就行了。
code
1 #include<cstdio> 2 #include<algorithm> 3 #include<stack> 4 #define maxn 200090 5 6 using namespace std; 7 8 int n,y,tot,ans=999999,dfs_clock,scc_cnt,tong[maxn],dfn[maxn],low[maxn],head[maxn],scc[maxn]; 9 stack<int>s; 10 struct node{ 11 int to,next; 12 }edge[maxn]; 13 14 void add(int x,int y) 15 { 16 edge[++tot].to=y; 17 edge[tot].next=head[x]; 18 head[x]=tot; 19 } 20 21 void dfs(int p) 22 { 23 dfn[p]=low[p]=++dfs_clock; 24 s.push(p); 25 for(int i=head[p];i;i=edge[i].next) 26 { 27 int y=edge[i].to; 28 if(!dfn[y]) 29 { 30 dfs(y); 31 low[p]=min(low[p],low[y]); 32 } 33 else if(!scc[y]) low[p]=min(low[p],dfn[y]); 34 } 35 if(dfn[p]==low[p]) 36 { 37 scc_cnt++; 38 while(1) 39 { 40 int x=s.top(); 41 s.pop(); 42 scc[x]=scc_cnt; 43 if(x==p) break; 44 } 45 } 46 } 47 48 int main() 49 { 50 scanf("%d",&n); 51 for(int i=1;i<=n;i++) 52 scanf("%d",&y),add(i,y); 53 for(int i=1;i<=n;i++) 54 { 55 if(!dfn[i]) dfs(i); 56 tong[scc[i]]++; 57 } 58 for(int i=1;i<=scc_cnt;i++) if(tong[i]>1&&tong[i]<ans) ans=tong[i]; 59 printf("%d ",ans); 60 return 0; 61 }
以上是关于强连通分量初探 By cellur925的主要内容,如果未能解决你的问题,请参考以下文章