LGP11189 [KDOI R10] 水杯降温 学习笔记
\(\texttt{Luogu Link}\)
前言
不拖啦。
题意简述
有一棵 \(n\) 个结点的树,以 \(1\) 为根。每个结点 \(u\) 有权值(水杯),初始为 \(a_u\)。
你有两种操作,可进行任意次:
- 加热:子树全体加 \(1\)。
- 吹风:从某叶子到根直链全体减 \(1\)。
问能否通过操作使得最后所有权值均为 \(0\)。
\(n\le 10^5\),\(a_i\le 10^{12}\)。
做法解析
以及分析这整道题的内容,你会发现:有的是两种“区间”改,最终对某种状态的要求;没有的是区间求和,树路径信息;看起来不太有的是树上 \(\texttt{DP}\) 值的维护。这导向树上差分。
对于父子 \((u,v)\),设 \(d_v=a_v-a_u\),特别地定义 \(d_1=a_1\)。在这个视角下,对 \(u\) 子树全体加,相当于把 \(d_u\) 加一;对 \(u\) 到根直链全体减 \(1\),就是对于这直链上所有的结点 \(u\),对于它们所有不在直链上的儿子 \(v\),将 \(d_v\) 加一。
你容易发现,所有 \(d_u\) 的值全都是只增不减的。这样,无解的判定很简单了:终局是要求所有 \(a_u=0\) 的,这相当于要求 \(a_1=0\) 且所有 \(d_u=0\),因此,若存在 \(d_u>0\) 则无解(此处 \(u\neq 1\))。
进行了上面的判断后,那什么时候有解呢?首先如果初始 \(a_1\le 0\) 那就必然有解,因为我们可以先对着 \(1\) 子树加若干次把 \(a_1\) 加到 \(0\),再对着下面所有 \(d_u<0\) 的结点子树加到 \(d_u=0\)。这是简单的。
相对困难的是 \(a_1>0\) 的情形。这意味着我们必须先对着 \(1\) 吹若干次风,把 \(a_1\) 吹到 \(0\),再对着下面所有 \(d_u<0\) 的结点子树加到 \(d_u=0\)。问题在于,我们这些风要对着哪吹呢?吹风会使“很多”的 \(d_u\) 增大,我们不能让 \(d_u\) 被加到大于 \(0\)。
于是,我们设 \(f_u\) 表示,只考虑 \(u\) 的子树,对着这个子树内的点最多可以吹风多少次。显然对于 \(f_u\) 来说 \(f_v\) 都是自己的子问题,所以这个东西从可以从下往上转移。
具体来说,我们来考虑一下 \(f_u\) 取值有什么限制。\(f_u\) 条吹风直链经过 \(u\) 时,对于它的一个儿子 \(v\) 来说,若有 \(g_v\) 条吹风直链经过它,就意味着有 \(f_u-g_v\) 条直链从它旁边擦肩而过。由我们之前的分析,这意味着 \(d_v\) 会被加上 \(f_u-g_v\) 次。那么显然我们要求:\(\forall v\in son[u]\),\(f_u-g_v\le |d_v|\),当然还有 \(g_v\le f_v\)。因此总的限制就是:\(f_u-|d_v|\le g_v\le f_v\),\(\sum g_v\le f_u\),\(f_u\le \sum f_v\)。
如果你特别糖,你可能会问:为什么 \(f_u\le \sum f_v\)?难道不能从 \(u\) 吹一些风吗?
确实不能。题目说的是“吹风:从某叶子到根直链全体减 \(1\)”。你是不是把“叶子”两个字看掉了。
显然 \(f_u\) 越小条件越好满足,\(f_u\) 是可二分的。所以二分就完了。
代码实现
Inf
只开了 \(10^{13}\) 而非 \(10^{18}\)。原因是我的 fss
是直接加的,如果叶子太多 Inf
又太大可能在二分内或者求 fss
处加爆 long long
。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e5+5;
const lolo Inf=1e14;
int N,X,tfa[MaxN];
lolo A[MaxN],D[MaxN],F[MaxN],fss[MaxN];
vector<int> Tr[MaxN];
bool check(int u,lolo lim){lolo tsum=0;for(auto v : Tr[u])tsum+=max(0ll,lim-D[v]);return tsum<=lim;
}
void dfs(int u){if(!Tr[u].size()){F[u]=Inf;return;}fss[u]=0;for(int v : Tr[u])dfs(v),fss[u]+=F[v];lolo sl=0,sr=min(Inf,fss[u]),smid,sres=-1;for(auto v : Tr[u])minner(sr,F[v]+D[v]);while(sl<=sr){smid=(sl+sr)>>1;if(check(u,smid))sres=smid,sl=smid+1;else sr=smid-1;}F[u]=sres;
}
void mian(){readi(N);for(int i=1;i<=N;i++)Tr[i].clear();for(int i=2;i<=N;i++)readi(tfa[i]),Tr[tfa[i]].push_back(i);for(int i=1;i<=N;i++)readi(A[i]);for(int i=1;i<=N;i++)D[i]=A[i]-A[tfa[i]];for(int i=2;i<=N;i++)if(D[i]>0){puts("Shuiniao");return;}for(int i=1;i<=N;i++)D[i]=abs(D[i]);if(A[1]<0){puts("Huoyu");return;}dfs(1);puts(F[1]>=A[1]?"Huoyu":"Shuiniao");
}
int Tpn,Tcn;
int main(){readis(Tpn,Tcn);while(Tcn--)mian();return 0;
}