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;
}

  

posted @ 2018-09-13 16:59  OYJason  阅读(268)  评论(0编辑  收藏  举报