参考:http://hi.baidu.com/1093782566/blog/item/e5a0e9229913bd048b82a175.html
http://www.cppblog.com/IronOxide/archive/2010/08/16/123622.html?opt=admin
那么我们能否把图转化为树呢?首先可以想到的是,如果图中包含有环,那么就可以把这个环缩成一个点,因为环中的任意两个点可以到达,环中所有的点具有相同的性质,即它们分别能到达的点集都是相同的,能够到达它们的点集也是相同的。缩点后的图必无环,否则,可将环上所有点也缩成一个点,与极大强联通分量矛盾。
那么是否只有环中的点才具有相同的性质呢?进一步的考虑,图中的每一个极大强连通分支中的点都具有相同的性质。所以,如果把图中的所有极大强连通分支求出后,就可以把图收缩成一棵树,问题就迎刃而解了。
预备知识:有向图的强连通分量的求法,这个和求割点的算法差不多。零的点无法到达另一个出度为零的点;若cnt = 1 , 则该点所对应的强联通分量的点的个数即为答案。
4:如果图非连通,那么,至少存在两个独立的连通分量,问题一定无解。
#include <iostream> #include <stack> #include <cstring> using namespace std; const int MAXN = 10000 + 10; // 点的最大数量 const int MAXM = 50000 + 10; // 边的最大数量 // 假设对边u-->v struct EDGE { int v; // 从u点出发能到达的点v int next; // 从u点出发能到达的下一条边的编号 }; stack<int> s; EDGE edge[MAXM]; int low[MAXN]; // low[u]:是u或u的子树能够追溯到的最早的栈中节点的次序号 int dfn[MAXN]; // dfn[u]:节点u搜索的次序编号(时间戳) int first[MAXN]; // first[u] = e:从点u出发的最后一条边的编号是e(“最后”是指最后输入) int sccf[MAXN]; // sccf[i] = j:第i个点所在的强连通分量的编号 bool ins[MAXN]; // 是否在栈中 int outdegree[MAXN]; // 强连通分量的出度 int index; // 次序编号 int scc; // 强连通分量的数目 int n, m; void Init() { scc = 0; index = 1; memset(low, 0, sizeof(low)); memset(dfn, 0, sizeof(dfn)); memset(ins, false, sizeof(ins)); memset(sccf, 0, sizeof(sccf)); memset(first, -1, sizeof(first)); } void Tarjan(int u) { int v; low[u] = dfn[u] = index++; s.push(u); ins[u] = true; // 枚举每一条边:u-->v for (int k=first[u]; k!=-1; k=edge[k].next) { v = edge[k].v; if (dfn[v] == 0) { Tarjan(v); low[u]=min(low[u],low[v]); } else if (ins[v]) { low[u]=min(low[u],dfn[v]); } } // 如果节点u是强连通分量的根 if (dfn[u] == low[u]) { scc++; do { v = s.top(); s.pop(); ins[v] = false; sccf[v] = scc; }while (u != v); } } // 获得超级受喜欢的cows的数量 int GetSuperPopularNum() { int u, v; int cnt = 0; // 出度为0的强连通分量的数目 int ct[MAXN]; // ct[i] = j:强连通分量i有j个点 memset(outdegree, 0, sizeof(outdegree)); memset(ct, 0, sizeof(ct)); // 枚举每一个点u:求outdegree和ct for (u=1; u<=n; u++) { ct[sccf[u]]++; for (int k=first[u]; k!=-1; k=edge[k].next) { // 对每条边u-->v v = edge[k].v; if (sccf[u] != sccf[v]) { outdegree[sccf[u]]++; } } } // 数数强连通分量为0的点有多少个 for (u=1; u<=scc; u++) { if (outdegree[u] == 0) { cnt++; v = u; } } return (cnt == 1)? ct[v] : 0; } int main() { int i, u, v; int e = 0; // 边的数量,建图时会用到 // 初始化数据并建图 Init(); cin >> n >> m; for (i=0; i<m; i++) { cin >> u >> v; edge[e].v = v; edge[e].next = first[u]; first[u] = e; e++; } // 求强连通分量 for (i=1; i<=n; i++) { if (dfn[i] == 0) { Tarjan(i); } } // 输出答案 cout << GetSuperPopularNum() << endl; return 0; }