【BZOJ4712】洪水
Description
原题链接(权限题)
题目大意:给一棵带点权的树,要求支持两种操作:
(1)给某一个点的权值加上\(x\)
(2)令堵上一个点的代价为其权值,询问以某一个点\(u\)为根的子树中,至少花费多少代价,使得\(u\)与子树中的叶子节点不连通(不算\(u\),可以赌叶子节点和\(u\)本身)
点数\(n \le 200000\)
Solution
静态DP很好做:记\(u\)的点权为\(w_u\),设\(f_u\)表示\(u\)子树的答案,则有
现在要支持点权修改,怎么实现?这种题貌似叫动态DP,可以用树剖的套路来解决。
令\(son_u\)表示\(u\)的重儿子,我们把重儿子的权值分离出来:
令后面一个和式为\(g_u\),则有
使用树链剖分维护两个值:每个点\(u\)的\(w_u\)与\(g_u\)。
接下来,在此表示法的意义下,我们考虑询问\(u\)时,其答案\(f_u\)具体怎么体现。根据套路,我们要尽可能用一种线段树能维护的问题形式表现答案。我们发现,\(f_u\)所含的比较的元素可以展开来看(其中\(v_i\)表示\(u\)的\(i\)级重儿子):
这其实就是一个最小前缀和的形式,只不过前缀和的最后一个元素是该位置的\(w\),其余元素是\(g\)。这其实很好实现,用线段树来维护每一条重链的最小前缀和。具体实现上,两两重链之间插入一个特殊点,其权值设为正无穷,以限制考虑的前缀不可在链之间跨越。
所以,\(w\)和\(g\)是真正记录在树剖中的数据,而每个点\(u\)的\(f_u\),则是体现为从\(u\)点走重链向下的最小前缀和。
修改上,也是这类问题的一贯做法。首先单独修改\(u\)的权值\(w_u\)。根据定义,其他点的\(w\)一定不会受到影响。当下唯一影响的,就是\(u\)所在链链顶元素的\(g\)值。我们计算出于\(u\)修改\(w_u\)后与修改前,链顶\(f\)值的变化量。这个变化量只会影响到链顶的父亲——另一条重链上的某一个点的\(g\)值,在线段树上修改。我们发现,这时候我们的操作模式已经和“单独修改\(u\)的权值\(w_u\)”一模一样了,只不过初始时我们修改\(w_u\)通过线段树上推的方式间接影响全局,而现在我们通过直接修改\(g_u\)的方式直接影响全局。因此计算出该点修改前后,其链顶的\(f\)的差值,继续向上重复这一过程即可。
可以发现,这一过程发生的次数,就是从初始点往根节点走遇到的轻边数量。由于每一个点到根节点经过的轻链条数为\(\mathcal O(\log n)\),因此单次修改的复杂度是\(\mathcal O(\log ^2 n)\)。
在做这题之前,我已经做过了类似的一道动态DP题,并且很快地想到了将重儿子的数据单独分离的套路。可是我在探究答案究竟怎么体现的时候卡住了,没有发现“最小前缀和”的形式,所以没有写出来。
Code
#include <cstdio>
using namespace std;
typedef long long ll;
const int N=400005;
const ll INF=1LL<<30;
int n;
ll w[N],f[N],g[N];
int h[N],tot;
struct Edge{
int v,next;
}e[N*2];
int pre[N],dep[N],size[N],son[N],who[N],loc[N],locnt,top[N];
inline ll min(ll x,ll y){
return x<y?x:y;
}
inline void addEdge(int u,int v){
e[++tot]=(Edge){v,h[u]}; h[u]=tot;
e[++tot]=(Edge){u,h[v]}; h[v]=tot;
}
void readData(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",w+i);
int u,v;
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
addEdge(u,v);
}
}
void d_dfs1(int u,int fa){
pre[u]=fa;
dep[u]=dep[fa]+1;
son[u]=0;
size[u]=1;
g[u]=0;
bool have=false;
for(int i=h[u],v;i;i=e[i].next)
if((v=e[i].v)!=fa){
have=true;
d_dfs1(v,u);
size[u]+=size[v];
if(!son[u]||size[v]>size[son[u]])
son[u]=v;
g[u]+=f[v];
}
if(!have)
f[u]=w[u], g[u]=INF;
else
f[u]=min(w[u],g[u]);
}
void d_dfs2(int u,int _top){
top[u]=_top;
loc[u]=++locnt;
who[locnt]=u;
if(!son[u]){
who[++locnt]=0;
return;
}
g[u]-=f[son[u]];
d_dfs2(son[u],_top);
for(int i=h[u],v;i;i=e[i].next)
if((v=e[i].v)!=pre[u]&&v!=son[u])
d_dfs2(v,v);
}
namespace SEG{/*{{{*/
const int S=N*2;
int rt,sz;
int ch[S][2];
ll sum[S],best[S];
void pushup(int u){
sum[u]=sum[ch[u][0]]+sum[ch[u][1]];
best[u]=min(best[ch[u][0]],sum[ch[u][0]]+best[ch[u][1]]);
}
void build(int &u,int l,int r){
u=++sz;
if(l==r){
int x=who[l];
sum[u]=g[x];
best[u]=w[x];
return;
}
int mid=(l+r)>>1;
build(ch[u][0],l,mid);
build(ch[u][1],mid+1,r);
pushup(u);
}
void modify(int u,int l,int r,int pos,ll delta){
if(l==r){
int x=who[l];
best[u]=w[x];
sum[u]+=delta;
return;
}
int mid=(l+r)>>1;
if(pos<=mid)
modify(ch[u][0],l,mid,pos,delta);
else
modify(ch[u][1],mid+1,r,pos,delta);
pushup(u);
}
ll query(int u,int l,int r,int L,ll &lsum){
if(L<=l){
ll res=lsum+best[u];
lsum+=sum[u];
return res;
}
int mid=(l+r)>>1;
ll res=INF;
if(L<=mid)
res=min(res,query(ch[u][0],l,mid,L,lsum));
res=min(res,query(ch[u][1],mid+1,r,L,lsum));
return res;
}
ll queryBest(int l){
ll lsum=0;
return query(rt,1,locnt,l,lsum);
}
}/*}}}*/
void initTree(){
d_dfs1(1,0);
d_dfs2(1,1);
w[0]=INF;
SEG::build(SEG::rt,1,locnt);
}
void modify(int u,ll x){
ll delta=0,oldval,newval;
w[u]+=x;
for(;u;u=pre[top[u]]){
oldval=SEG::queryBest(loc[top[u]]);
SEG::modify(SEG::rt,1,locnt,loc[u],delta);
newval=SEG::queryBest(loc[top[u]]);
delta=newval-oldval;
if(!delta) break;
}
}
void answerQuery(){
int q;
char opt[2];
ll x,y;
scanf("%d",&q);
while(q--){
scanf("%s%lld",opt,&x);
if(opt[0]=='Q')
printf("%lld\n",SEG::queryBest(loc[x]));
else{
scanf("%lld",&y);
modify(x,y);
}
}
}
int main(){
readData();
initTree();
answerQuery();
return 0;
}