bzoj4712 洪水 树链剖分

Description


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

n<=200000,保证任意to都为非负数

Solution


考虑没有修改的情况,设f[i]表示以i为根的子树不能通向叶节点最小代价,h[i]=Σf[son[i]],w[i]为去掉节点i的代价,那么f[i]=min(h[i],w[i])
加上修改操作,我们增大节点i的w会影响fa[i]当且仅当h[fa[i]]+delta<w[fa[i]],并且一定会影响一整条链
那么我们可以轻重链剖分然后线段树记录区间w[i]-h[i]的最小值,我们只需要找到第一个w[i]-h[i]>delta的位置然后区间加即可

不是特别好写,日到底的线段树不是特别优秀

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)

typedef long long LL;
const LL INF=1e15;
const int N=200005;
const int E=400005;

struct edge {int x,y,next;} e[E];

LL mn[N<<2],tag[N<<2],w[N],h[N],f[N];
int fa[N],bl[N],dep[N],size[N];
int pos[N],dfn[N];
int ls[N],edCnt;

LL read() {
	LL x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void add_edge(int x,int y) {
	e[++edCnt]=(edge) {x,y,ls[x]}; ls[x]=edCnt;
	e[++edCnt]=(edge) {y,x,ls[y]}; ls[y]=edCnt;
}

void dfs1(int now) {
	size[now]=1;
	for (int i=ls[now];i;i=e[i].next) {
		if (e[i].y==fa[now]) continue;
		dep[e[i].y]=dep[now]+1;
		fa[e[i].y]=now;
		dfs1(e[i].y);
		size[now]+=size[e[i].y];
		h[now]+=f[e[i].y];
	}
	if (size[now]==1) h[now]=INF;
	f[now]=std:: min(w[now],h[now]);
}

void dfs2(int now,int up) {
	pos[now]=++pos[0]; bl[now]=up;
	dfn[pos[now]]=now;
	int mx=0;
	for (int i=ls[now];i;i=e[i].next) {
		if (e[i].y!=fa[now]&&size[e[i].y]>size[mx]) mx=e[i].y;
	}
	if (!mx) return ;
	dfs2(mx,up);
	for (int i=ls[now];i;i=e[i].next) {
		if (e[i].y!=fa[now]&&e[i].y!=mx) dfs2(e[i].y,e[i].y);
	}
}

void push_down(int now) {
	if (!tag[now]) return ;
	tag[now<<1]+=tag[now]; tag[now<<1|1]+=tag[now];
	mn[now<<1]-=tag[now]; mn[now<<1|1]-=tag[now];
	tag[now]=0;
}

LL get_h(int now,int tl,int tr,int x) {
	if (tl!=tr) push_down(now);
	if (tl==tr) {
		mn[now]=w[dfn[tl]]-tag[now];
		return tag[now];
	}
	LL ret; int mid=(tl+tr)>>1;
	if (x<=mid) ret=get_h(now<<1,tl,mid,x);
	else ret=get_h(now<<1|1,mid+1,tr,x);
	mn[now]=std:: min(mn[now<<1],mn[now<<1|1]);
	return ret;
}

int modify(int now,int tl,int tr,int l,int r,LL v) {
	if (tl!=tr) push_down(now);
	int mid=(tl+tr)>>1,ret;
	if (tl==l&&tr==r) {
		if (mn[now]>=v) {
			tag[now]+=v; mn[now]-=v;
			return 0;
		} else if (l==r) {
			tag[now]+=v; mn[now]-=v;
			return dfn[l];
		}
		ret=modify(now<<1|1,mid+1,tr,mid+1,r,v);
		if (!ret) ret=modify(now<<1,tl,mid,l,mid,v);
		mn[now]=std:: min(mn[now<<1],mn[now<<1|1]);
		return ret;
	}
	if (r<=mid) {
		ret=modify(now<<1,tl,mid,l,r,v);
	} else if (l>mid) {
		ret=modify(now<<1|1,mid+1,tr,l,r,v);
	} else {
		ret=modify(now<<1|1,mid+1,tr,mid+1,r,v);
		if (!ret) ret=modify(now<<1,tl,mid,l,mid,v);
	}
	mn[now]=std:: min(mn[now<<1],mn[now<<1|1]);
	return ret;
}

void build(int now,int tl,int tr) {
	if (tl==tr) {
		int x=dfn[tl];
		tag[now]=h[x];
		mn[now]=w[x]-h[x];
		return ;
	}
	int mid=(tl+tr)>>1;
	build(now<<1,tl,mid);
	build(now<<1|1,mid+1,tr);
	mn[now]=std:: min(mn[now<<1],mn[now<<1|1]);
}

void change(int x,LL v) {
	if (v<=0) return ;
	int ret=0;
	while (x) {
		ret=modify(1,1,pos[0],pos[bl[x]],pos[x],v);
		if (ret) break;
		x=fa[bl[x]];
	}
	if (ret) change(fa[ret],w[ret]-get_h(1,1,pos[0],pos[ret])+v);
}

int main(void) {
	int n=read();
	rep(i,1,n) w[i]=read();
	rep(i,2,n) add_edge(read(),read());
	dep[1]=1; dfs1(1); dfs2(1,1);
	build(1,1,n);
	for (int T=read();T--;) {
		char opt[2]; scanf("%s",opt);
		if (opt[0]=='Q') {
			int x=read();
			LL ans=std:: min(get_h(1,1,n,pos[x]),w[x]);
			printf("%lld\n", ans);
		} else if (opt[0]=='C') {
			int x=read(); LL y=read();
			w[x]+=y; LL h=get_h(1,1,n,pos[x]);
			if (w[x]-y<h) change(fa[x],std:: min(h,w[x])-std:: min(h,w[x]-y));
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值