题目
分析
最开始想的是拓扑排序,因为拓扑序列满足如果一个点u可以到v,那么u一定在v的前面。如果求出了拓扑序列,发现序列中u在v的前面,那么就可以认为u可以到达v了。
但是随后想到两个问题
- 如果有环的怎么办呢,就不能求拓扑序列了。
- 虽然这个图存在拓扑序列1-2-3,但是我们能仅仅因为1在2的前面就认为1可以到2吗?显然不行。也就是说拓扑排序只能断言u不能到v,但是不能断言u可以到v。
第一个问题(图中有环,无法拓扑排序)很简单,用Tarjan算法把强联通分量缩成一个点即可,缩点后的图肯定是没有环的。Tarjan(黑)算(匣)法(子)模板如下
int DFN[N], LOW[N];
stack<int> Stack;
bool InStack[N];
int belong[N]; // belong[u]为原图u在缩点后的新图中的编号
int scc_num; // 强连通分量的数目
int index;
void dfs(int u)
{
DFN[u] = LOW[u] = ++index;
InStack[u] = true;
Stack.push(u);
for (unsigned i= 0; i < G1[u].size(); i++)
{
int v = G1[u][i];
if (!DFN[v])
{
dfs(v);
LOW[u] = min(LOW[u], LOW[v]);
}
else if (InStack[v])
LOW[u] = min(LOW[u], DFN[v]);
}
if (DFN[u] == LOW[u]) // 找到了一个新的强连通分量
{
scc_num++; // 强连通分量数目
int tmp;
do // 这个循环可以输出当前强连通分量,tmp为连通分量内的点。
{
tmp = Stack.top();
Stack.pop();
belong[tmp] = scc_num; // 原图tmp这个点属于第scc_num个分量
InStack[tmp] = false;
}while (u != tmp);
}
}
void Tarjan()
{
memset(InStack, false, sizeof(InStack));
while (!Stack.empty()) Stack.pop();
memset(DFN, 0, sizeof(DFN));
memset(LOW, 0, sizeof(LOW));
scc_num = 0;
index = 0;
for (int i = 1; i <= n; i++)
if (!DFN[i])
dfs(i);
}
第二个问题(如何判断图的单向联通性。即任意两个点u,v。u到v或v到u的路径至少存在一条)其实也很好解决,虽然拓扑序列不能断言一个点可以到达另一个点,但是我们可以从另一个角度利用拓扑排序。回忆一下拓扑排序的过程,每次选择一个入度为0的点放进拓扑序列,并且把这个点相关的边都删除掉。那么如果我们同时找到了两个入度为0的点说明了什么呢?那就说明这两个点只能通过之前删除的点到达,这两个点互相是不能到达的,这样一来就解决了问题。
其实判断图的单向联通性还可以通过最长路来判断。我们都知道如果一个图是强联通图,那么一定存在一条经过所有点至少一次的回路。类似的单向联通图有没有什么性质呢?其实有的,那就是单向联通图最长路的长度等于点的数目。
下面来证明一下这个定理。假设有一个9个点的DAG,最长路为8,即1->2->3……->8,如下图。
现在想想我们要证明的是什么,我们要证明的是9号点不可能和其他8个点全部单向联通,否则这个图就是单向联通图了。使用反证法,假设9号点和其他8个点全部单向联通,来推出矛盾。
首先考虑1和9。因为1和9单向联通,所以1和9之间肯定有边,而且方向肯定不是9->1,否则最长路的长度就变成了9,所以边是1->9。
再来考虑8和9。8,9之间的边肯定不是8->9,否则最长路的长度就变成了9,所以边是9->8。
再来考虑2和9。2,9之间的边肯定不是9->2,否则最长路的长度就变成了9,所以边是2->9。
依次类推,可以推导出下图。
但是我们发现,虽然每一步都在防止最长路变成9,但是最后这个图的最长路还是变成了9。所以不论如何,只要我们假定9号点和其他八个点都单向联通,最后肯定会推出矛盾!
所以最后的结论就是:如果DAG是单向联通图,则该DAG的最长路长度等于点的数目。后面附上最长路的代码。
代码
#include <cstring>
#include <cstdio>
#include <vector>
#include <stack>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1005;
vector<int> G1[N];
vector<int> G2[N];
int flag[N][N];
int dp[N];
int n, m;
int DFN[N], LOW[N];
stack<int> Stack;
bool InStack[N];
int belong[N];
int scc_num;
int index;
void dfs(int u)
{
DFN[u] = LOW[u] = ++index;
InStack[u] = true;
Stack.push(u);
for (unsigned i= 0; i < G1[u].size(); i++)
{
int v = G1[u][i];
if (!DFN[v])
{
dfs(v);
LOW[u] = min(LOW[u], LOW[v]);
}
else if (InStack[v])
LOW[u] = min(LOW[u], DFN[v]);
}
if (DFN[u] == LOW[u])
{
scc_num++;
int tmp;
do
{
tmp = Stack.top();
Stack.pop();
belong[tmp] = scc_num;
InStack[tmp] = false;
}while (u != tmp);
}
}
void Tarjan()
{
memset(InStack, false, sizeof(InStack));
while (!Stack.empty()) Stack.pop();
memset(DFN, 0, sizeof(DFN));
memset(LOW, 0, sizeof(LOW));
scc_num = 0;
index = 0;
for (int i = 1; i <= n; i++)
if (!DFN[i])
dfs(i);
}
int DP(int u)
{
if (dp[u]) return dp[u];
int ans = 1;
for (unsigned i = 0; i < G2[u].size(); i++)
{
int v = G2[u][i];
ans= max(ans, DP(v) + 1);
}
return dp[u] = ans;
}
int main()
{
//freopen("test.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &n, &m);
for (int i = 0; i < N; i++)
{
G1[i].clear();
G2[i].clear();
}
for(int i = 0; i < m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
G1[u].push_back(v);
}
Tarjan();
memset(flag, 0, sizeof(flag));
for (int u = 1; u <= n; u++)
for (unsigned i = 0; i < G1[u].size(); i++)
{
int v = G1[u][i];
if (!flag[belong[u]][belong[v]] && belong[u] != belong[v])
{
flag[belong[u]][belong[v]] = 1;
G2[belong[u]].push_back(belong[v]);
}
}
int ans = -1;
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= scc_num; i++)
ans = max(ans, DP(i));
if (ans == scc_num) printf("Yes\n");
else printf("No\n");
}
return 0;
}