之前也稍稍看过第一点强联通分量,但当时并不是多么的理解。所以近期有复习了一遍强联通分量,现在才理解了强联通分量的算法-----trajan
Trajan算法
用两个数组dfn[i]记录到达节点v的时间,low[i]表示通过他的自己点可以到达所有点中的最小时间,即low[i]=min(low[i],low[u]);u为v的了孙,初始化low[v]=dfn[u]。如果low[v]比dfn[v]小,说明v可以通过它的子结点u,u1,u2...到达它的祖先v',则存在环,这个环上所有的点组成的子图便是一个强连通分量。换一个角度看,如low[v]==dfn[v]时,则它的子树中所有low[u]==dfn[v]的点都与v构成一个环,维护一个栈,DFS过程中,每遍历一个点则把它放入栈中,当发现low[v]==dfn[v]则依次把栈里的元素都弹出来,当栈顶元素为v时结束,这些点便构成一个以v为树根的强连通分量。
模板详见>>Shadow传从门
例题解析:
noip2010模拟赛classroom
描述 Description
在幻想乡,上白泽慧音是以知识渊博闻名的老师。春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄。因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点。人间之里由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。
输入格式 Input Format
第1行:两个正整数N,M
第2..M+1行:每行三个正整数a,b,t, t = 1表示存在从村庄a到b的单向道路,t = 2表示村庄a,b之间存在双向通行的道路。保证每条道路只出现一次。
输出格式 Output Format
第1行: 1个整数,表示最大的绝对连通区域包含的村庄个数。
第2行:若干个整数,依次输出最大的绝对连通区域所包含的村庄编号。
样例输入 Sample Input
5 5
1 2 1
1 3 2
2 4 2
5 1 2
3 5 1
样例输出 Sample Output
3
1 3 5
时间限制 Time Limitation
1s
注释 Hint
对于60%的数据:N <= 200且M <= 10,000
对于100%的数据:N <= 5,000且M <= 50,000
来源 Source
noip2010 模拟赛
思路:trajan的模板题,先用trajan求出有向图的强联通分量。然后记录最多的在一个强联通分量的点就行了(详细见代码)
代码如下:
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<string> #include<stack> #define MAXN 50100 using namespace std; inline int read() { char ch=getchar(); int x=0,f=1; while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } struct shadow { int y,next,v; }a[MAXN*4]; stack<int> s; int len=0,lin[MAXN*4]; int h[MAXN*2]; int n,m; void insert(int x,int y) { a[++len].next=lin[x]; a[len].y=y; lin[x]=len; } int vis[MAXN*2],dfn[MAXN*2],low[MAXN*2],num,sum,ans=0; void trajan(int x) { dfn[x]=low[x]=++num; s.push(x); for(int i=lin[x];i;i=a[i].next) { int g=a[i].y; if(!dfn[g]) { trajan(g); low[x]=min(low[x],low[g]); } else if(!vis[g]) { low[x]=min(low[x],dfn[g]); } } if(dfn[x]==low[x]) { sum++; while(1) { int t=s.top(); s.pop(); vis[t]=sum; if(t==x) break; } } } int main() { memset(lin,0,sizeof(lin)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); n=read();m=read(); for(int i=1;i<=m;i++) { int x,y,t; x=read();y=read();t=read(); if(t==1) insert(x,y); if(t==2) { insert(x,y); insert(y,x); } } sum=0,num=0; for(int i=1;i<=n;i++) if(!dfn[i]) trajan(i); int t=0; for(int i=1;i<=n;i++) h[vis[i]]++; int maxx=0; for(int i=1;i<=sum;i++) if(h[i]>maxx)//找出最大的强联通分量 maxx=h[i],t=i; cout<<maxx<<endl; for(int i=1;i<=n;i++) { if(vis[i]==t)//如果这个点在强联通分量中则输出 cout<<i<<' '; } cout<<endl; return 0; }
usaco 2004 popular(最受欢迎的牛)
描述 Description
每头牛都有一个梦想:成为一个群体中最受欢迎的名牛!在一个有N(1<=N<=10,000)头牛的牛群中,给你M(1<=M<=50,000)个二元组(A,B),表示A认为B是受欢迎的。既然受欢迎是可传递的,那么如果A认为B受欢迎,B又认为C受欢迎,则A也会认为C是受欢迎的,哪怕这不是十分明确的规定。你的任务是计算被所有其它的牛都喜欢的牛的个数。
输入格式 Input Format
第一行,两个数,N和M。第2~M+1行,每行两个数,A和B,表示A认为B是受欢迎的。
输出格式 Output Format
一个数,被其他所有奶牛认为受欢迎的奶牛头数。
样例输入 Sample Input
3 3
1 2
2 1
2 3
样例输出 Sample Output
1
样例说明
3号奶牛是唯一被所有其他奶牛认为有名的。
时间限制 Time Limitation
1s
注释 Hint
10%的数据N<=20, M<=50
30%的数据N<=1000,M<=20000
70%的数据N<=5000,M<=50000
100%的数据N<=10000,M<=50000
来源 Source
usaco 2004 popular
思路:先用trajan求出有向图的强联通分量,然后因为强联通分量中如果有一个点x可以到达点y,那么这个强联通分量中的所有点都可以到达点y。利用这个性质,我们可以用Kosaraju中的DFS_T来求有几头牛是最受欢迎的(Kosaraju模板详见>>Shadow传送门)
代码如下:
#include<iostream> #include<cmath> #include<cstring> #include<algorithm> #include<cstdio> #define MAXN 50100 #include<stack> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } struct shadow { int y,next,v; }a[MAXN*4]; stack<int> s; int n,m; int lin[MAXN*4],len=0; void insert(int x,int y) { a[++len].y=y; a[len].next=lin[x]; lin[x]=len; } int dfn[MAXN*2],low[MAXN*2],num=0,sum=0,ans=0,vis[MAXN*2]; int h[MAXN*2],k[MAXN*2]; bool vis1[MAXN*2],vis2[MAXN*2],ri[MAXN*2]; void trajan(int x) { dfn[x]=low[x]=++num; s.push(x); for(int i=lin[x];i;i=a[i].next) { int g=a[i].y; if(!dfn[g]) { trajan(g); low[x]=min(low[x],low[g]); } else if(!vis[g]) low[x]=min(low[x],dfn[g]); } if(low[x]==dfn[x]) { sum++; for(;;) { int t=s.top(); s.pop(); vis[t]=sum; h[sum]++; if(t==x) break; } } } void dfs(int x,int r) { ri[x]=1; if((vis[x]!=r)&&(!vis1[vis[x]])) { k[vis[x]]++; vis1[vis[x]]=1; } for(int i=lin[x];i;i=a[i].next) { int g=a[i].y; if(!ri[g]) dfs(g,r); } } int main() { memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); n=read();m=read(); for(int i=1;i<=m;i++) { int x,y; x=read();y=read(); insert(x,y); } for(int i=1;i<=n;i++) if(!dfn[i]) trajan(i); for(int i=1;i<=n;i++) { if(!vis2[vis[i]]) { memset(ri,0,sizeof(ri)); memset(vis1,0,sizeof(vis1)); dfs(i,vis[i]); vis2[vis[i]]=1; } } for(int i=1;i<=n;i++) { if(k[vis[i]]==sum-1) { cout<<h[vis[i]]<<endl; return 0; } } cout<<0<<endl; return 0; }
usaco 5.3.3校园网络
描述 Description
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意如果 B 在 A 学校的分发列表中,那么 A 不必也在 B 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
输入格式 Input Format
第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。
输出格式 Output Format
第一行应该包括一个正整数:子任务 A 的解。第二行应该包括子任务 B 的解。
样例输入 Sample Input
5
2 4 3 0
4 5 0
0
0
1 0
样例输出 Sample Output
1
2
时间限制 Time Limitation
1s
来源 Source
usaco 5.3.3
思路:第一问就是用trajan算法缩点,然后求出入度为零的强联通分量的个数。第二问因为你要向图中加最少的边,所以计算图中的所有出度和入度为零的点中找出出度入度为零的点的个数最大值。注意:如果这图本身就是一个强联通分量那么就不用添加任何的边
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<cstdio> #include<string> #include<stack> #define MAXN 11000 using namespace std; inline int read() { char ch=getchar(); int x=0,f=1; while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } struct shadow { int y,next; }a[MAXN]; stack<int> s; int lin[MAXN],len=0; void insert(int x,int y) { a[++len].next=lin[x]; a[len].y=y; lin[x]=len; } int n; int dfn[MAXN],low[MAXN],num=0,vis[MAXN],sum=0; int ru[MAXN],chu[MAXN]; void trajan(int x) { low[x]=dfn[x]=++num; s.push(x); for(int i=lin[x];i;i=a[i].next) { int g=a[i].y; if(!dfn[g]) { trajan(g); low[x]=min(low[x],low[g]); } else if(!vis[g]) low[x]=min(low[x],dfn[g]); } if(low[x]==dfn[x]) { sum++; while(1) { int t=s.top(); s.pop(); vis[t]=sum; if(t==x) break; } } } int main() { memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); n=read(); for(int i=1;i<=n;i++) { for(;;) { int x; cin>>x; if(x==0) break; insert(i,x); } } for(int i=1;i<=n;i++) if(!dfn[i]) trajan(i); for(int i=1;i<=n;i++) for(int j=lin[i];j;j=a[j].next) if(vis[i]!=vis[a[j].y]) ru[vis[a[j].y]]++,chu[vis[i]]++; int ans=0,ans1=0,ans2=0; for(int i=1;i<=sum;i++) { if(ru[i]==0) ans++,ans1++; if(chu[i]==0) ans2++; } ans2=max(ans2,ans1); if(sum==1) ans2=0; cout<<ans<<endl; cout<<ans2<<endl; return 0; }
noip2015信息传递
这个题我就不说了,就是一个trajan的模板放上去就A了,这道题还有好多种写法什么dfs了,模拟了,并查集了等等哇,比起2016年的day1t2不知道简单到哪里去了