BZOJ4712 洪水
Description
小A走到一个山脚下,准备给自己造一个小屋。这时候,小A的朋友(op,又叫管理员)打开了创造模式,然后飞到山顶放了格水。于是小A面前出现了一个瀑布。作为平民的小A只好老实巴交地爬山堵水。那么问题来了:我们把这个瀑布看成是一个$n$个节点的树,每个节点有权值(爬上去的代价)。小A要选择一些节点,以其权值和作为代价将这些点删除(堵上),使得根节点与所有叶子结点不连通。问最小代价。不过到这还没结束。小A的朋友觉得这样子太便宜小A了,于是他还会不断地修改地形,使得某个节点的权值发生变化。不过到这还没结束。小A觉得朋友做得太绝了,于是放弃了分离所有叶子节点的方案。取而代之的是,每次他只要在某个子树中(和子树之外的点完全无关)。于是他找到你。
Input
输入文件第一行包含一个数$n$,表示树的大小,其中$1$号点是根。
接下来一行包含$n$个数,表示第$i$个点的权值。
接下来$n-1$行每行包含两个数$fr$,$to$。表示树中有一条边$(fr,to)$。
接下来一行一个整数,表示操作的个数。
接下来$m$行每行表示一个操作,若该行第一个字符为Q,则表示询问操作,后面跟一个参数$x$,表示对应子树的根;若为C,则表示修改操作,后面接两个参数$x$,$to$,表示将点$x$的权值加上$to$。
$n,m\leq 200000$,保证任意$to$都为非负数
Output
对于每次询问操作,输出对应的答案,答案之间用换行隔开。
题目大意
有一棵树,给每一个点涂色都有一个代价,求最小的代价使得每一个点的到根的路径上都至少有一个点被染色,有修改点的代价、询问子树答案两种操作,其中修改点的代价只会增加。
如果不加修改就是一个简单的的$Dp$,加上修改也可以从转移方程入手。
设$w[x]$表示给$x$点染色的代价。
设$F[x]$表示使得以$x$为根的子树符合要求的最小染色代价。
设$G[x]$表示所有$x$的儿子节点$k$的$F[k]$之和。
特别的,对于叶子节点$t$,$F[t]=w[t],G[t]=INF$。
所以有$F[x]=min(w[x],G[x])$。
$w[x]$可以$O(1)$修改,难点在于动态维护$G[x]$的值。
考虑维护$C[x]=w[x]-G[x]$,当我们增加了$x$的代价$d$时$$,影响的只有从$x$到根路径上的点。
其中一定会有一段与$x$相邻的路径,其中每一个点$k$都满足$C[k]\geq d$,这部分的点满足$G[k]+d\leq w[k]$,我们只需要将这些点的$C[k]$减去$d$即可,表示这些点的$F$值增加了$d$,假定这是情况一。
然后就是出现了第一个$C[k]<d$的点,表示通过这次增加代价,$G[k]$超过了$w[k]$,这时$F[k]$的增量就是$C[k]$,而$C[k]$增加的值仍是$d$,再递归处理即可,视为情况二。
注意当递归时增量为负,表示$w[x]<G[x]$,增加了$G[x]$的值对$x$以及$x$的祖先无影响,就不再继续修改,看作情况三。
这样的复杂度会不会炸掉呢?答案显然是否定的。修改时,若未出现情况二,则没有递归处理,根据重链剖分,复杂度为$O(log^2n)$。否则,若出现了情况二,则下次再处理这个点时就会以情况三出现,也就是每个点最多贡献一次递归的层数。当然,增加一个点的$w[x]$会使这个点再度有可能贡献一层,所以递归的层数不超过$n+m$,每层的复杂度为$O(log^2n)$,复杂度为$O((n+m)log^2n)$。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define INF 1000000000000000ll #define mid ((l+r)>>1) #define LL long long #define M 500020 using namespace std; LL read(){ LL nm=0,fh=1; char cw=getchar(); for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0'); return nm*fh; } void write(LL x){if(x>9) write(x/10); putchar(x%10+'0');} LL n,m,fs[M],nt[M<<1],to[M<<1],tp[M],fa[M],tmp; void link(LL x,LL y){nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y;} LL cnt,dfn[M],tk[M],u,v,mxs[M],sz[M],dep[M]; LL w[M],f[M],g[M],rm[M<<2],tg[M<<2]; char ch[20]; void dfs1(LL x,LL last){ f[x]=w[x],sz[x]=1,fa[x]=last; if(nt[fs[x]]==-1&&x!=1){g[x]=INF;return;} for(LL i=fs[x];i!=-1;i=nt[i]){ if(to[i]==last) continue; dfs1(to[i],x),sz[x]+=sz[to[i]],g[x]+=f[to[i]]; if(sz[to[i]]>sz[mxs[x]]) mxs[x]=to[i]; } f[x]=min(f[x],g[x]); } void dfs2(LL x,LL dtp){ tp[x]=dtp,dfn[x]=++cnt,tk[cnt]=x; if(mxs[x]) dfs2(mxs[x],dtp); for(LL i=fs[x];i!=-1;i=nt[i]) if(!dfn[to[i]]) dfs2(to[i],to[i]); } void pushdown(LL x){ rm[x<<1]-=tg[x],rm[x<<1|1]-=tg[x]; tg[x<<1]+=tg[x],tg[x<<1|1]+=tg[x],tg[x]=0; } void pushup(LL x){rm[x]=min(rm[x<<1],rm[x<<1|1]);} void build(LL x,LL l,LL r){ if(l==r){rm[x]=w[tk[l]]-g[tk[l]];return;} build(x<<1,l,mid),build(x<<1|1,mid+1,r),pushup(x); } LL upd(LL x,LL l,LL r,LL L,LL R,LL dt){ if(l==r){rm[x]-=dt,tg[x]+=dt;return rm[x]<=0?tk[l]:0ll;} if(L<=l&&r<=R&&rm[x]>=dt){rm[x]-=dt,tg[x]+=dt;return 0;} pushdown(x); LL now=0; if(R>mid) now=upd(x<<1|1,mid+1,r,L,R,dt); if(L<=mid&&!now) now=upd(x<<1,l,mid,L,R,dt); pushup(x); return now; } LL getnum(LL x,LL l,LL r,LL pos){ if(l==r) return rm[x]; pushdown(x); if(pos<=mid) return getnum(x<<1,l,mid,pos); else return getnum(x<<1|1,mid+1,r,pos); } void solve(LL x,LL dt){ if(!x||dt<=0) return; for(;x;x=fa[tp[x]]){ LL now=upd(1,1,n,dfn[tp[x]],dfn[x],dt); if(now) return solve(fa[now],getnum(1,1,n,dfn[now])+dt); } } int main(){ n=read(),memset(fs,-1,sizeof(fs)); for(LL i=1;i<=n;i++) w[i]=read(); for(LL i=1;i<n;i++) u=read(),v=read(),link(u,v),link(v,u); dfs1(1,0),dfs2(1,1),build(1,1,n); for(LL T=read();T;T--){ scanf("%s",ch),u=read(); if(ch[0]!='Q'){ m=read(),w[u]+=m,upd(1,1,n,dfn[u],dfn[u],-m); solve(fa[u],min(w[u],w[u]-getnum(1,1,n,dfn[u]))-w[u]+m); } else m=min(w[u],w[u]-getnum(1,1,n,dfn[u])),write(m),putchar('\n'); } return 0; }