题解: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;
}

浙公网安备 33010602011771号