题解:luoguP16899 [GKS 2022 #H] Electricity
题意解析
给定 \(n\) 个节点以及其电容量 \(A[i]\),随后给出 \(n - 1\) 条边,表示对应的起点和终点相通。但对于任意的 \(x, y\) 小于等于 \(n\),只有当 \(A[y]\) 严格小于 \(A[x]\) 时且 \(x\) 与 \(y\) 直接互通时,\(x\) 才能向 \(y\) 传递能量。
算法解析
对于这种表示关系的题目,我们第一时间就会联想到 拓扑排序。
一种很容易想到的写法是对于任意的 \(x, y\) 小于等于 \(n\),只有当 \(A[y]\) 严格小于 \(A[x]\) 时且 \(x\) 与 \(y\) 直接互通时,把 \(x\) 向 \(y\) 连边。然后定义 \(f[i]\) 为到 \(i\) 这里前面(加上 \(i\))最多一共有多少收到电力的点。设父节点为 \(u\),子节点为 \(v\),则转移方程为 \(f[v]=max(f[v],f[u]+1)\),统计时统记出度为零的点中最大的 \(f[i]\) 值。
但略加思考过后就会发现这种做法不可行,因为不同的出度为零的点可能有同一个父节点而被同时接受,所以会出现错误。
所以我们考虑建反边,维护父节点的信息,这样就能避免问题了。什么意思呢,就是当 \(A[x]\) 严格小于 \(A[y]\) 且 \(x\) 与 \(y\) 互通时才把 \(x\) 向 \(y\) 连边,这样做就是建反边了。随后的定义和上面一样,但是转移略有不同,因为是实际上 汇总 子节点信息,所以转移方程为 \(f[v]+=f[u]\) (即是加上子节点贡献),汇总就是统计出度为零的点的最大的 \(f[i]\) 值,因为上面建的是反边,所以不会有错。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
int T, n, casenum, f[MAXN], a[MAXN], ind[MAXN];
vector<int> e[MAXN]; // 存边的邻接表
void Topo() {
queue<int> q;
for (int i = 1; i <= n; i++) {
f[i] = 1; // 每个节点自己要算上
if (!ind[i]) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : e[u]) {
ind[v]--;
f[v] += f[u]; // 实际上是父节点汇总子节点信息
if (ind[v] == 0) q.push(v);
}
}
}
int main() {
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; ind[i] = 0, e[i].clear(), i++) cin >> a[i]; // 多测清空
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
if (a[u] < a[v]) {
e[u].push_back(v);
ind[v]++;
} // 建反边
if (a[v] < a[u]) {
e[v].push_back(u);
ind[u]++;
} // 两种情况都要考虑
}
cout << "Case #" << ++casenum << ": ";
Topo();
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (!e[i].size()) {
cnt = max(cnt, f[i]);
}
} // 汇总信息
cout << cnt << '\n';
}
return 0;
}

浙公网安备 33010602011771号