FOI2025 day5——图论 II
数据结构--强连通分量
城市轰炸【JZOJ5452】
题目描述
小奇有 \(n\) 座城市,城市之间建立了 \(m\) 条有向的地下通道。战狂会发起若干轮轰炸,每轮可以轰炸任意多个城市。
每座城市里都有战狂部署的间谍,在城市遭遇轰炸时,它们会通过地下通道撤离至其它城市。非常不幸的是,在地道里无法得知其它城市是否被轰炸。如果存在两个不同的城市 \(i\),\(j\),它们在同一轮被轰炸,并且可以通过地道从城市 \(i\) 到达城市 \(j\),那么城市 \(i\) 的间谍可能因为撤离到城市 \(j\) 而被炸死。为了避免这一情况,战狂不能在同一轮轰炸城市 \(i\) 和城市 \(j\)。
你需要求出战狂最少需要多少轮可以对每座城市都进行至少一次轰炸。
输入格式
第一行两个整数 \(n,m\)。
接下来 \(m\) 行每行两个整数 \(a,b\) 表示一条从 \(a\) 连向 \(b\) 的单向边。
输出格式
一行一个整数表示答案。
样例
样例输入
5 4
1 2
2 3
3 1
4 5
样例输出
3
数据范围与提示
对于 \(20\%\) 的数据,\(n,m\le 10\)。
对于 \(40\%\) 的数据,\(n,m\le 1000\)。
对于另外 \(30\%\) 的数据,保证无环。
对于 \(100\%\) 的数据,\(n,m\le 1000000\)。
一、算法思路
- 强连通分量压缩
由于在同一个强连通分量(SCC)内任意两点 \(u,v\) 都互相可达,不能放在同一轰炸轮次里。
因此,先用 Tarjan(或 Kosaraju)算法把原图压缩成 SCC,记每个 SCC 的大小为 \(w_u\) 。 - 构建有向无环图(DAG)
把每个 SCC 看成一个“超节点”,在它们之间连边,得到一个 DAG。
在这个 DAG 中,若超节点 \(u\to v\) ,那么在同一轮次也不能同时轰炸它们。 - 求带权最长路径
将每个超节点 \(u\) 的权重设为 \(w_u\) ,问题就变成:在这个带权 DAG 上,把所有超节点分成若干轮次(每轮选一个反链 antichain),最少需要几轮?
根据 Dilworth 定理(推广到带权情况),最小轮次数等于 DAG 上最大「链」所能累积的权重之和,也就是带权最长路径。
所以我们在 DAG 上做一次拓扑排序,然后做 DP:\[ \mathrm{dp}[u] = w_u + \max_{p\to u} \mathrm{dp}[p]. \]答案就是所有 \(\mathrm{dp}[u]\) 的最大值。
二、复杂度分析
- 建图、Tarjan: \(O(n + m)\)
- 构造 DAG、拓扑 + DP: \(O(n + m)\)
- 总体 \(O(n+m)\) ,可以跑到 \(n,m\le10^6\) 。
// 本程序思路:
// 1. 使用 Tarjan 算法分解原有向图的强连通分量(SCC),将互相可达的城市归为一个超节点。
// 在同一个 SCC 内任意两城市互相可达,因此不能同一轮次同时轰炸。
// 2. 构建压缩后的有向无环图(DAG):遍历原图的所有边,若边的两个端点属于不同 SCC,
// 则在对应超节点之间添加一条有向边,并增加目标节点的入度(不做去重)。
// 3. 带权最长路径 DP:每个超节点的权重为其包含的城市数量。对 DAG 做拓扑排序,
// 初始化 dp[u] = 权重[u]。对于拓扑序中每条边 u->v,
// 更新 dp[v] = max(dp[v], dp[u] + 权重[v])。最终答案为所有 dp[u] 的最大值,
// 即最少需要的轰炸轮次。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 1000000 + 5;
const int MAXM = 1000000 + 5;
// 原图邻接表
int head[MAXN], to[MAXM], nxt[MAXM], ec = 0;
// 压缩后 DAG 邻接表
vector<int> dag[MAXN];
// Tarjan 相关
int dfn[MAXN], low[MAXN], ts = 0;
bool in_stk[MAXN];
int stk[MAXN], top = 0;
int scc_id[MAXN], scc_cnt = 0;
int scc_sz[MAXN];
// DP
ll dp[MAXN];
int indeg[MAXN];
int n, m;
// 加边函数
void add_edge(int u, int v) {
to[ec] = v;
nxt[ec] = head[u];
head[u] = ec++;
}
// Tarjan 算法分解 SCC
void tarjan(int u) {
dfn[u] = low[u] = ++ts;
stk[top++] = u;
in_stk[u] = true;
for (int e = head[u]; e != -1; e = nxt[e]) {
int v = to[e];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stk[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
++scc_cnt;
int x;
do {
x = stk[--top];
in_stk[x] = false;
scc_id[x] = scc_cnt;
++scc_sz[scc_cnt];
} while (x != u);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 读入城市数和通道数
cin >> n >> m;
fill(head, head + n + 1, -1);
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
add_edge(a, b);
}
// 1. Tarjan 分解 SCC
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i);
}
// 2. 构建 DAG(不去重)
for (int u = 1; u <= n; u++) {
int su = scc_id[u];
for (int e = head[u]; e != -1; e = nxt[e]) {
int v = to[e];
int sv = scc_id[v];
if (su != sv) {
dag[su].push_back(sv);
indeg[sv]++;
}
}
}
// 3. 拓扑排序 + 带权最长路径 DP
queue<int> q;
for (int u = 1; u <= scc_cnt; u++) {
dp[u] = scc_sz[u];
if (indeg[u] == 0) q.push(u);
}
ll ans = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
ans = max(ans, dp[u]);
for (int v : dag[u]) {
dp[v] = max(dp[v], dp[u] + (ll)scc_sz[v]);
if (--indeg[v] == 0) q.push(v);
}
}
// 输出最少轮次
cout << ans << "\n";
return 0;
}

浙公网安备 33010602011771号