BZOJ 4712 洪水

Description

小A走到一个山脚下,准备给自己造一个小屋。这时候,小A的朋友(op,又叫管理员)打开了创造模式,然后飞到山顶放了格水。于是小A面前出现了一个瀑布。作为平民的小A只好老实巴交地爬山堵水。那么问题来了:我们把这个瀑布看成是一个n个节点的树,每个节点有权值(爬上去的代价)。小A要选择一些节点,以其权值和作为代价将这些点删除(堵上),使得根节点与所有叶子结点不连通。问最小代价。不过到这还没结束。小A的朋友觉得这样子太便宜小A了,于是他还会不断地修改地形,使得某个节点的权值发生变化。不过到这还没结束。小A觉得朋友做得太绝了,于是放弃了分离所有叶子节点的方案。取而代之的是,每次他只要在某个子树中(和子树之外的点完全无关)。于是他找到你。


Input

输入文件第一行包含一个数n,表示树的大小。

接下来一行包含n个数,表示第i个点的权值。
接下来n-1行每行包含两个数fr,to。表示书中有一条边(fr,to)。
接下来一行一个整数,表示操作的个数。
接下来m行每行表示一个操作,若该行第一个数为Q,则表示询问操作,后面跟一个参数x,表示对应子树的根;若为C,则表示修改操作,后面接两个参数x,to,表示将点x的权值加上to。
n,m<=200000,保证任意to都为非负数


Output

对于每次询问操作,输出对应的答案,答案之间用换行隔开。


Solution

树剖优化dp。
点i的答案设为dp[i],权值设为a[i]
d p [ i ] = m i n ( a [ i ] , ∑ d p [ s o n ] ) dp[i]=min(a[i],\sum dp[son]) dp[i]=min(a[i]dp[son])
C操作只会增加x到根的儿子答案和,且有递增性。
那么维护每个点的 t [ i ] = a [ i ] − ∑ d p [ s o n ] t[i]=a[i]-\sum dp[son] t[i]=a[i]dp[son],若t[i]<0则取a[i],否则取 ∑ d p [ s o n ] \sum dp[son] dp[son]
然后就随便维护了(

注意在一个点的dp从 ∑ d p [ s o n ] \sum dp[son] dp[son]变成 a [ i ] a[i] a[i]时向上的修改变为 t o + a [ i ] − ∑ d p [ s o n ] to+a[i]-\sum dp[son] to+a[i]dp[son],因为在dp过程中要突破a[i]需要耗费一定的修改量。(可以手摸看看)
所以要分段进行操作,但因为每个点只会改变一次取值,所以时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)


Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct edge{
	int y,nxt;
}e[400010];
int head[200010],cnt;
ll tree[4000010];
ll laz[4000010];
int dep[200010];
int son[200010];
int siz[200010]; 
int top[200010];
ll dis[200010];
ll dp[200010];
int id[200010];
int fa[200010];
int p[200010];
ll a[200010];
int n,m,rp,tot,ans;
bool flag;
void adde(int x,int y){
	cnt++;
	e[cnt].y=y;
	e[cnt].nxt=head[x];
	head[x]=cnt;
}
void build(int id,int l,int r){  
    if(l==r){  
    	int x=p[l];
    	laz[id]=dp[x];
        tree[id]=a[x]-dp[x];
        //cout<<l<<" "<<x<<" "<<tree[id]<<" "<<a[x]<<" "<<dp[x]<<endl;
        return;  
    }  
    int mid=(l+r)/2;  
    build(id*2,l,mid);  
    build(id*2+1,mid+1,r);  
    tree[id]=min(tree[id<<1],tree[id<<1|1]);
} 
void pushdown(int id,int l,int r){
    if(laz[id]){  
    	tree[id<<1]-=laz[id];
        tree[id<<1|1]-=laz[id];
        laz[id<<1]+=laz[id];
		laz[id<<1|1]+=laz[id];
        laz[id]=0;
    }  
}  
int change(int id,int l,int r,int x,int y,int z){  
	int ans=0;
	int mid=(l+r)>>1;
	if(l<r) pushdown(id,l,r);
    if(x<=l&&r<=y){
    	if(tree[id]>=z){
    		tree[id]-=z;
    		laz[id]+=z;
    		return 0;
		}
		if(l==r){
			tree[id]-=z;
    		laz[id]+=z;
    		return p[l];
		}
		ans=change(id<<1|1,mid+1,r,x,y,z);
		if(ans){
			tree[id]=min(tree[id<<1],tree[id<<1|1]);
			return ans;
		}
		ans=change(id<<1,l,mid,x,y,z);
		tree[id]=min(tree[id<<1],tree[id<<1|1]);
    	return ans;
	}
	if(y<=mid) ans=change(id<<1,l,mid,x,y,z); 
	else if(x>mid) ans=change(id<<1|1,mid+1,r,x,y,z);  
	else{
		ans=change(id<<1|1,mid+1,r,x,y,z);
		if(ans==0) ans=change(id<<1,l,mid,x,y,z);
	}
    tree[id]=min(tree[id<<1],tree[id<<1|1]);
    return ans;
}  
ll question(int id,int l,int r,int x){  
	if(l<r) pushdown(id,l,r);  
    if(l==r){
    	tree[id]=a[p[l]]-laz[id];
    	return laz[id];
	}
	ll ans=0;
    int mid=(l+r)/2;  
	if(x<=mid) ans=question(id<<1,l,mid,x);  
    else ans=question(id<<1|1,mid+1,r,x);
	tree[id]=min(tree[id<<1],tree[id<<1|1]);  
	return ans;
} 
void dfs1(int x,int father,int depth){
	//cout<<x<<endl;
	siz[x]=1;
	fa[x]=father;
	dep[x]=depth;
	int maxn=-1;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].y;
		if(y==father) continue;
		dfs1(y,x,depth+1);
		dp[x]+=min(dp[y],a[y]);
		siz[x]+=siz[y];
		if(siz[y]>maxn){
			maxn=siz[y];
			son[x]=y;
		}
	}
	if(siz[x]==1) dp[x]=1e16;
}
void dfs2(int x,int ltop){
	id[x]=++tot; 
	p[tot]=x;
	top[x]=ltop;
	if(!son[x]) return;
	dfs2(son[x],ltop);
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].y;
		if(y==fa[x]||y==son[x])
		continue;
		dfs2(y,y);
	}
}
void update(int x,int z){
	int y=0;
	if(z<=0) return;
	while(x){
		y=change(1,1,n,id[top[x]],id[x],z);
		if(y) break;
		x=fa[top[x]];
	}
	if(y) update(fa[y],a[y]-question(1,1,n,id[y])+z);
}
int main(){
	ll z;
	int x,y;
	char op[2];
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%lld",&a[i]);
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		adde(x,y),adde(y,x);
	}
	dfs1(1,0,0),dfs2(1,1);
	//for(int i=1;i<=n;i++)
	//cout<<i<<" "<<a[i]<<" "<<dp[i]<<" "<<id[i]<<endl;
	//cout<<endl;
	build(1,1,n);
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		scanf("%s%d",op,&x);
		if(op[0]=='C'){
			scanf("%d",&y);
			a[x]+=y;
			z=question(1,1,n,id[x]);
			if(a[x]-y<z) update(fa[x],min(a[x],z)-min(a[x]-y,z));
		}
		else printf("%lld\n",min(question(1,1,n,id[x]),a[x]));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值