题解: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;
}
posted @ 2026-08-18 19:46  Vicco_Li  阅读(2)  评论(0)    收藏  举报