题解:P11189 「KDOI-10」水杯降温

posted on 2024-10-14 01:15:02 | under | source

有趣的题目,考场想了个贪心但没敲,之后才发现可以二分。

显然和操作顺序没有关系,所以不妨将两个操作分开考虑。那么可以只通过子树加使得温度全 \(0\),当且仅当 \(\forall i,a_i\le a_{fa_i}\),这是因为父亲被加了儿子一定也被加;同时 \(\forall i,a_i\le 0\),即 \(a_1\le 0\)

再来考虑链减,\(a_1\) 被减的次数等价于总共进行了多少次链减。不妨记 \(w_i=a_{fa_i}-a_i\),每次链减会让所选路径上的所有点的相邻边(除了路径上的)都减一,我们要在 \(w\ge 0\) 的情况下尽量多链减。

考虑 dp,记 \(f_u\) 表示在 \(u\) 子树内所有边都非负的情况下最多进行几次链减,记 \(u\) 的儿子是 \(v_1\dots v_m\),边权分别为 \(w_1\dots w_m\)。那么每个 \(v_i\) 可以进行 \([0,f_{v_{i}}]\) 次操作,每次操作会导致 \(\forall j\ne i,w_j-1\)

\(b_i\) 表示 \(v_i\) 进行几次操作,那么问题等价于满足 \(b_i\in [0,f_{v_i}]\)\(\sum\limits_{j\ne i} b_j \le w_i\) 时最大化 \(\sum b_i\)。可以二分它,现在判定 \(S=\sum b_i\) 是否可行,上面两条限制等价于 \(\max(0,S-w_i)\le b_i\le f_{v_i}\),然后计算出 \(\sum b_i\) 的取值范围,看看 \(S\) 在不在里面就好。

代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int N = 1e5 + 5, inf = 1e13;
int n, fa[N], a[N], fl, T, f[N];
vector<int> to[N];

inline void dfs(int u){
	if(to[u].empty()) {f[u] = inf; return ;} 
	for(auto v : to[u]) dfs(v);
	
	int L = 0, R = inf, mid, fl, l, r;
	while(L + 1 < R){
		mid = L + R >> 1;
		
		fl = true;
		l = 0, r = 0;
		for(auto v : to[u]){
			int mi = max(0ll, mid - (a[u] - a[v]));
			if(mi > f[v]) {fl = false; break;}
			l += mi, r += f[v]; 
		}
		fl &= (l <= mid && mid <= r);
		
		if(fl) L = mid;
		else R = mid; 
	} 
	f[u] = L;
}
signed main(){
	cin >> T >> T;
	while(T--){
		scanf("%lld", &n), fl = true;
		for(int i = 1; i <= n; ++i) to[i].clear();
		for(int i = 2; i <= n; ++i) scanf("%lld", &fa[i]), to[fa[i]].push_back(i);
		for(int i = 1; i <= n; ++i){
			scanf("%lld", &a[i]);
			if(i > 1 && a[i] > a[fa[i]]) fl = false;
		}
		if(!fl) {puts("Shuiniao"); continue;}
		dfs(1);
		if(a[1] - f[1] <= 0) puts("Huoyu");
		else puts("Shuiniao");
	}
	return 0;
}
posted @ 2026-01-15 08:19  Zwi  阅读(2)  评论(0)    收藏  举报