CF999E 通往首都的道路
1 CF999E 通往首都的道路
2 题目描述
\(Berland\) 有个 \(𝑛\) 城市和 \(𝑚\) 条路。每条路都连接着一对城市。\(Berland\) 的道路是单向的。
为了从首都出发能到达所有的城市,最少需要修建多少条新路?新路也是单向的。
3 题解
一开始看到这道题,我想的是 \(dfs\) 判断有几个点无法从根节点到达,然后直接输出个数。但如果单纯地这么做,会导致一些问题:如果一些无法从根节点到达的点形成了一个强连通分量,那么只需要连接根节点与其中的任意一个点,就可以从根节点到达该强连通分量中的所有其他点。考虑到强连通分量,这道题的思路其实就很明晰了:我们将所有的强连通分量缩成一个点,然后再找所有无法被根节点到达的点的个数即可。
这里的第二步实际上不能光跑 \(dfs\)。经过观察,我们发现:如果有一个节点有一条指向其他节点的单向边,且两个节点都无法从根节点到达,我们实际上只用连一条边,将根节点与指向其他节点的节点连接即可。我们这时发现:如果一个节点入度为 \(0\) 且不为根节点,那么它肯定需要连接,否则肯定不需要连接。这是因为,如果入度不为 \(0\),那么指向这个节点的节点有可能入度为 \(0\),如果入度仍不 为 \(0\),那么我们只要一直回溯,最终一定会找到两种节点:入度为 \(0\) 的节点或者根节点。由于我们将所有入度为 \(0\) 的节点都与根节点连接了,而且根节点自身肯定与自身连接,所有入度不为 \(0\) 的节点肯定可以从根节点到达。缩点采用 \(Tarjan\) 即可。
4 代码(空格警告):
#include <iostream>
#include <vector>
using namespace std;
const int N = 5010;
int n, m, s, u, v, cnt, tot, num, top, cnt2;
int ver[N], head[N], last[N], dfn[N], low[N];
int stack[N], ins[N], c[N];
int deg[N];
vector<int> scc[N];
void add(int x, int y)
{
ver[++tot] = y;
last[tot] = head[x];
head[x] = tot;
}
void tarjan(int x)
{
dfn[x] = low[x] = ++num;
stack[++top] = x, ins[x] = 1;
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
if (!dfn[y])
{
tarjan(y);
low[x] = min(low[x], low[y]);
}
else if (ins[y]) low[x] = min(low[x], dfn[y]);
}
if (dfn[x] == low[x])
{
int y;
cnt++;
do
{
y = stack[top--], ins[y] = 0;
c[y] = cnt, scc[cnt].push_back(y);
}while (x != y);
}
}
int main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
cin >> u >> v;
add(u, v);
}
for (int i = 1; i <= n; i++)
{
if (!dfn[i]) tarjan(i);
}
deg[c[s]] = 0x3f3f3f3f;
for (int x = 1; x <= n; x++)
{
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
if (c[x] == c[y]) continue;
deg[c[y]]++;
}
}
for (int i = 1; i <= cnt; i++)
{
if (!deg[i]) cnt2++;
}
cout << cnt2;
return 0;
}