上白泽慧音
2017-09-19
题目描述
在幻想乡,上白泽慧音是以知识渊博闻名的老师。春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄。因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点。人间之里由N个村庄(编号为1..N)和M条道路组成,道路分为两种一种为单向通行的,一种为双向通行的,分别用1和2来标记。如果存在由村庄A到达村庄B的通路,那么我们认为可以从村庄A到达村庄B,记为(A,B)。当(A,B)和(B,A)同时满足时,我们认为A,B是绝对连通的,记为<A,B>。绝对连通区域是指一个村庄的集合,在这个集合中任意两个村庄X,Y都满足<X,Y>。现在你的任务是,找出最大的绝对连通区域,并将这个绝对连通区域的村庄按编号依次输出。若存在两个最大的,输出字典序最小的,比如当存在1,3,4和2,5,6这两个最大连通区域时,输出的是1,3,4。
输入输出格式
输入格式:
第1行:两个正整数N,M
第2..M+1行:每行三个正整数a,b,t, t = 1表示存在从村庄a到b的单向道路,t = 2表示村庄a,b之间存在双向通行的道路。保证每条道路只出现一次。
输出格式:
第1行: 1个整数,表示最大的绝对连通区域包含的村庄个数。
第2行:若干个整数,依次输出最大的绝对连通区域所包含的村庄编号。
输入输出样例
5 5 1 2 1 1 3 2 2 4 2 5 1 2 3 5 1
3 1 3 5
说明
对于60%的数据:N <= 200且M <= 10,000
对于100%的数据:N <= 5,000且M <= 50,000
强连通分量裸题
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<queue> #include<vector> #include<stack> using namespace std; const int maxn=5000+999; int read(){ int an=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while('0'<=ch&&ch<='9'){an=an*10+ch-'0';ch=getchar();} return an*f; } vector<int>b[maxn]; bool in[maxn]; int id[maxn],cnt,ans_T,P[maxn],low[maxn]; int ans[maxn],n,m; stack<int>q; void add(int x,int y){ b[x].push_back(y); } void tarjan(int x){ cnt++; low[x]=id[x]=cnt; q.push(x);in[x]=1; for(int i=0;i<b[x].size();i++){ int v=b[x][i]; if(!id[v]){ tarjan(v); low[x]=min(low[x],low[v]); } else if(in[v]){ low[x]=min(low[x],id[v]); } } if(id[x]==low[x]){ int cnt1=0; while(q.top()!=x){ cnt1++; in[q.top()]=0; ans[cnt1]=q.top(); q.pop(); } cnt1++; ans[cnt1]=q.top(); in[q.top()]=0; q.pop(); if(cnt1>ans_T){ ans_T=cnt1; for(int i=1;i<=cnt1;i++) P[i]=ans[i]; sort(P+1,P+1+cnt1); } } } int main(){ n=read();m=read(); for(int i=1;i<=m;i++){ int x,y,z; x=read();y=read();z=read(); add(x,y); if(z==2)add(y,x); } for(int i=1;i<=n;i++)if(!id[i])tarjan(i); cout<<ans_T<<endl; for(int i=1;i<=ans_T;i++)cout<<P[i]<<" "; }
by:s_a_b_e_r
Tarjan裸题+1
先安静的跑一遍Tarjan板子,对每一个搜到的SCC进行判断,如果大于当前答案那么更新答案
byvoid ←dalao的Tarjan讲解
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<stack> #include<vector> using namespace std; const int N=5009,M=500009; int n,m,cnt,p[N],ans; int ind,dfn[N],low[N]; bool in[N]; struct edge{int to,nex;}e[M<<1]; stack<int>s; vector<int>an,scc; void add(int u,int v){ e[++cnt]=(edge){v,p[u]}; p[u]=cnt; } void tarjan(int u) { dfn[u]=low[u]=++ind; in[u]=1;s.push(u); for(int i=p[u];i;i=e[i].nex) { int v=e[i].to; if(!dfn[v]){tarjan(v);low[u]=min(low[u],low[v]);} else if(in[v])low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { int v=0; scc.clear(); while(v!=u) { v=s.top();s.pop(); in[v]=0; scc.push_back(v); } if(scc.size()>an.size()) { ans=scc.size(); sort(scc.begin(),scc.end()); an.clear(); for(int i=0;i<scc.size();++i) an.push_back(scc[i]); } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;++i) { int a,b,t; scanf("%d%d%d",&a,&b,&t); if(t==1)add(a,b); else{add(a,b);add(b,a);} } for(int i=1;i<=n;++i) if(!dfn[i])tarjan(i); cout<<ans<<endl; for(int i=0;i<ans;++i)cout<<an[i]<<" "; return 0; }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1.算法简介 官方(?)定义——“Tarjan 算法:一种由Robert Tarjan提出的求解有向图强连通分量的算法。” ……什么叫“强连通分量”? 这玩意网上真是怎么说的都有啊,查到了好多东西,稍微整合一下…… 在有向图G中,如果两个顶点vi,vj间有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点“强连通”。 如果有向图G的每两个顶点都强连通,那么称G是一个“强连通图”。 如果这个有向图G中的一个子图满足每两个点强连通,那么我们把这个子图称为“强连通子图”。(单独一个点也是) G中的一个极大强连通子图(使最大的环最大),称为一个“强连通分量”。 (可以理解为环,孤立的点是自环) //后期:↑上面这段我自己都不知道我自己在说啥……什么时候语文好了改下 tarjan里还有一些其他需要用到的定义: dfn[i]:在dfs中该节点被搜索的次序(又名时间戳) low[i]表示i所能直接或间接达到的、dfn值最小的顶点。(实际操作中low[i]不一定最小,但不会影响程序的最终结果) 当dfn[i]==low[i]时,以i为根的搜索子树上所有节点是一个强连通分量。 2.算法流程 其实说白了还是dfs。 除此之外要用到栈……指的不是递归中的系统栈,是自己加的栈。 首先定义一些dfs树上的边的概念…… 树枝边:指dfs过程中的边 ——就是构成dfs树的边。 横叉边:连接的两个点没有父子关系的边 ——遇到横叉边怎么办? ——采取无视大法。(当然这是后话 后向边:由子节点指向父节点的边 ——只有这种边能产生环(即产生强连通子图)。 从起始点开始dfs。 每dfs到一个点u,标记dfn,low赋初值=dfn,把u入栈。 然后,开始枚举从u出发的每条边,检查这条边指向的点v。 如果v没有被访问过的话,说明这条边是树枝边,dfs这个点v(递归),递归结束回溯时更新low[u] = min(low[u], low[v]) 如果v还在栈中的话,说明这条边是后向边,更新low[u] = min(low[u], dfn[v]) 除此之外的情况,即v已经进过栈并且出了栈的情况,说明这条边是横叉边,既不能向下dfs,也不会产生环,于是采取无视大法OvO 以上处理完后,如果u的dfn值依然等于low值的话,即u点所能指向的最小的点是它本身 只有两种情况能做到这一点 其一,这是孤立的一个点; 其二,形成了一个指回u点的环,所以u及u的dfs子树是一个强连通分量。 这时候就可以把栈中u及u的子树项(如果有的话)全部弹出了。 一开始推荐的那个网页里有详细的算法流程演示,超清楚qwq。
by:wypx