有向图的缩点就是把有向图中强连通分量缩成一个点(道理很简单,我到了这个强连通分量的任何一点,那么这个强连通分量上的点就都能被我访问了),在处理有向图的连通性问题时有很多作用。
代码是对求连通分量的改的,cnt做连通量的编号,belong[],表示点属于哪个连通分量,vector<> p存储每个连通量的点。
int Stack[maxn], low[maxn], dfn[maxn], inStack[maxn], belong[maxn]; int now, cnt; // now:时间戳,cnt强连通的个数 vector<int> g[maxn]; stack<int> s; vector<int> p[maxn];void init(){ now = cnt = 0; memset(inStack, 0,sizeof(inStack)); memset(belong, 0,sizeof(belong)); memset(dfn, 0,sizeof(dfn)); memset(low, 0,sizeof(low)); memset(out, 0,sizeof(out)); for(int i=0;i<maxn;i++) { g[i].clear(); p[i].clear(); } } void tarjan(int u){ // 打上标记,入栈 low[u] = dfn[u] = ++now; s.push(u); inStack[u] = 1; for (int i = 0; i < (int)g[u].size(); ++i) { int v = g[u][i]; if (!dfn[v]) { //未访问过 tarjan(v); low[u] = min(low[u], low[v]); //low,dfn(v)中可能是最小的 } else if (inStack[v]) { //访问过,还在栈中 low[u] = min(low[u], dfn[v]); //dfn (v)中找最小 } } if (dfn[u] == low[u]) { ++cnt; // 统计个数 int v=-1; while (v!= u) { v = s.top(); s.pop(); belong[v] = cnt; inStack[v] = 0; p[cnt].push_back(v); } } }
习题
(1.
给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,
输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
第一行为两个整数 1 ≤ n, m ≤ 105,
接下来 M 行,每行两个整数 1 ≤ u, v ≤ 105 表示从点 u 至点 v 有一条有向边。
数据保证没有重边、自环。
第一行输出一个整数 z,表示作为答案的点集的大小;
第二行输出 z 个整数,升序排序,表示作为答案的点集。
解析
首先输出的点集,应该是有每个缩点中的任意一个点组成的;如果一个a缩点内的一点能够到达另一个b缩点内的点,说明缩点b可以通过缩点a到达,b就不需要考虑了.
依照这两点我们就可以构造一个数组link[]:
for() //遍历每个u for() //遍历边u-v if() //如果u和v不属于同一个强连通量 link[belong[v]]=true //包含v的连通量成无关
这里可能会想如果v的连通分量有另一点可以到u的分量,会不会造成两个连通量都遮蔽了。其实不会,如果存在这样的点,那么这两个连通量也变成了一个大的连通量,但是tarjan得到的就是最大的强连通量
c++代码
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+100; int low[maxn], dfn[maxn], inStack[maxn], belong[maxn],link[maxn] ; int now, cnt; // now:时间戳,cnt强连通的个数 vector<int> g[maxn]; stack<int> s; vector<int> p[maxn]; vector<int> ans; void init(){ now = cnt = 0; memset(inStack, 0,sizeof(inStack)); memset(belong, 0,sizeof(belong)); memset(dfn, 0,sizeof(dfn)); memset(low, 0,sizeof(low)); memset(link, 0,sizeof(link)); for(int i=0;i<maxn;i++) { g[i].clear(); p[i].clear(); } ans.clear(); } void tarjan(int u){ // 打上标记,入栈 low[u] = dfn[u] = ++now; s.push(u); inStack[u] = 1; for (int i = 0; i < (int)g[u].size(); ++i) { int v = g[u][i]; if (!dfn[v]) { //未访问过 tarjan(v); low[u] = min(low[u], low[v]); //low,dfn(v)中可能是最小的 } else if (inStack[v]) { //访问过,还在栈中 low[u] = min(low[u], dfn[v]); //dfn (v)中找最小 } } if (dfn[u] == low[u]) { ++cnt; // 统计个数 int v=-1; while (v!= u) { v = s.top(); s.pop(); belong[v] = cnt; inStack[v] = 0; p[cnt].push_back(v); } } } int main(){ init(); int n,m; scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); g[u].push_back(v); } for (int i=1;i<=n;i++) { if (!dfn[i]) tarjan(i); } for(int u=1;u<=n;u++){ for(int j=0;j<g[u].size();j++){ int v=g[u][j]; if(belong[u]!=belong[v]){ link[belong[v]]=1; } } } for (int i=1;i<=cnt;i++) sort(p[i].begin(),p[i].end()); for (int i=1;i<=cnt;i++) { if (link[i]==0) ans.push_back(p[i][0]);//选取第一个点就好了 } sort(ans.begin(),ans.end()); printf("%d\n",ans.size()); for (int i=0;i<ans.size();i++) { printf("%d%c",ans[i],(i==ans.size()-1)?'\n':' '); } return 0; }
(2
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow.
Input
* Lines 2..1+M: Two space-separated numbers A and B, meaning that A thinks B is popular.
Output
Sample Input
3 3
1 2
2 1
2 3
Sample Output
1
Hint
#include<iostream> #include<stdio.h> #include<string.h> #include<vector> #include<stack> using namespace std; const int maxn=2010; int low[maxn], dfn[maxn], inStack[maxn], belong[maxn]; int now, cnt; // now:时间戳,cnt强连通的个数 vector<int> g[maxn]; stack<int> s; void init() { now = cnt = 0; memset(inStack, 0,sizeof(inStack)); memset(belong, 0,sizeof(belong)); memset(dfn, 0,sizeof(dfn)); memset(low, 0,sizeof(low)); for(int i=0;i<maxn;i++) g[i].clear(); } void tarjan(int u) { // 打上标记,入栈 low[u] = dfn[u] = ++now; s.push(u); inStack[u] = 1; for (int i = 0; i < (int)g[u].size(); ++i) { int v = g[u][i]; if (!dfn[v]) { //未访问过 tarjan(v); low[u] = min(low[u], low[v]); //low,dfn(y)中可能是最小的 } else if (inStack[v]) { //访问过,还在栈中 low[u] = min(low[u], dfn[v]); //dfn } } // 回溯,如果当前节点的dfn = low 表示栈中形成一个强连通分量 if (dfn[u] == low[u]) { ++cnt; // 统计个数 int v=-1; while (v!= u) { v = s.top(); s.pop(); belong[v] = cnt; inStack[v] = 0; } } } int main(){ int n,m; while(scanf("%d%d",&n,&m)!=EOF){ init(); if(n==0&&m==0) break; int f1,f2,p1,p2; for(int i=1;i<=m;i++){ scanf("%d%d",&f1,&f2); scanf("%d%d",&p1,&p2); p1=f1*2+p1,p2=f2*2+p2; g[p1].push_back(p2^1); g[p2].push_back(p1^1); } for(int i=0;i<2*n;i++){ if(!dfn[i]) tarjan(i); } int flag=0; // for(int i=0;i<2*n;i++) cout<<belong[i]<<endl; for(int i=0;i<2*n;i+=2){ if(belong[i]==belong[i^1]) { flag=1; break; } } if(!flag) cout<<"YES"<<endl; else cout<<"NO"<<endl; } }