[BZOJ4712]洪水

bzoj

description

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

Input

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

Output

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

Sample Input

4
4 3 2 1
1 2
1 3
4 2
4
Q 1
Q 2
C 4 10
Q 1

Sample Output

3
1
4

sol

树链剖分+线段树维护动态\(dp\)。关于动态\(dp\)的内容可以去参考\(WC2018\)cjk的课件。
考虑没有修改怎么做。设\(f[i]\)表示把\(i\)子树中的叶子节点全部与\(i\)分离的最小代价,那么转移就是\(f[i]=\min(v[i],\sum_{j} f[j])\),其中\(j\)\(i\)的儿子。
我们把这棵树树链剖分一下,设\(g[i]\)表示\(i\)的所有轻儿子的\(f\)值之和,那么转移就变成了:\(f[i]=\min(v[i],g[i]+f[son[i]])\)
把一条重链当作是一个一维的序列。这样我们要求的东西就类似于一个最小前缀和,可以用线段树维护一下。
对于一次修改,显然只有这个点的所有祖先的\(dp\)值会受到影响。我们每次算出一个点的修改对这个点所在重链的链顶的影响,求出这个链顶的\(dp\)值,再拿链顶去更新它的父亲。
对于一次询问,我们只要在线段树里查这个点到所在链的链底一段的\(dp\)值就行了。
由于最多只会跳\(\log n\)次重链,每次要在线段树里查一段区间的\(dp\)值,所以复杂度是\(O(n\log^2n)\)

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
ll gi(){
	ll x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
const int N = 2e5+5;
int n,q,to[N<<1],nxt[N<<1],head[N],cnt,fa[N],dep[N],sz[N],son[N],top[N],dfn[N],End[N];
ll a[N],dp[N];
struct data{
	ll g,v;
	data(){g=v=0;}
	data(ll gg,ll vv){g=gg,v=vv;}
	data operator + (const data &b) const
		{return data(g+b.g,min(v,g+b.v));}
}t[N<<2],val[N];
void link(int u,int v){
	to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
void dfs1(int u,int f){
	fa[u]=f;dep[u]=dep[f]+1;sz[u]=1;
	for (int e=head[u];e;e=nxt[e])
		if (to[e]!=f){
			dfs1(to[e],u);sz[u]+=sz[to[e]];
			if (sz[to[e]]>sz[son[u]]) son[u]=to[e];
		}
}
void dfs2(int u,int up){
	top[u]=up;dfn[u]=++cnt;
	if (son[u]) dfs2(son[u],up);else End[top[u]]=cnt;
	for (int e=head[u];e;e=nxt[e])
		if (to[e]!=fa[u]&&to[e]!=son[u])
			dfs2(to[e],to[e]);
}
void dfs(int u){
	if (son[u]) dfs(son[u]),dp[u]+=dp[son[u]];else dp[u]=1e9;
	for (int e=head[u];e;e=nxt[e])
		if (to[e]!=fa[u]&&to[e]!=son[u])
			dfs(to[e]),dp[u]+=dp[to[e]],val[dfn[u]].g+=dp[to[e]];
	dp[u]=min(dp[u],a[u]);
}
void build(int x,int l,int r){
	if (l==r) {t[x]=val[l];return;}
	int mid=l+r>>1;
	build(x<<1,l,mid);build(x<<1|1,mid+1,r);
	t[x]=t[x<<1]+t[x<<1|1];
}
void modify(int x,int l,int r,int p){
	if (l==r) {t[x]=val[l];return;}
	int mid=l+r>>1;
	if (p<=mid) modify(x<<1,l,mid,p);
	else modify(x<<1|1,mid+1,r,p);
	t[x]=t[x<<1]+t[x<<1|1];
}
data query(int x,int l,int r,int ql,int qr){
	if (l>=ql&&r<=qr) return t[x];
	int mid=l+r>>1;
	if (qr<=mid) return query(x<<1,l,mid,ql,qr);
	if (ql>mid) return query(x<<1|1,mid+1,r,ql,qr);
	return query(x<<1,l,mid,ql,qr)+query(x<<1|1,mid+1,r,ql,qr);
}
void work(int u,ll v){
	val[dfn[u]].v+=v;ll t=0;
	while (u){
		t=query(1,1,n,dfn[top[u]],End[top[u]]).v;
		modify(1,1,n,dfn[u]);
		v=query(1,1,n,dfn[top[u]],End[top[u]]).v-t;
		u=fa[top[u]];val[dfn[u]].g+=v;
	}
}
int main(){
	n=gi();
	for (int i=1;i<=n;++i) a[i]=gi();
	for (int i=1;i<n;++i){
		int u=gi(),v=gi();
		link(u,v);link(v,u);
	}
	dfs1(1,0),cnt=0,dfs2(1,1),dfs(1);
	for (int i=1;i<=n;++i) val[dfn[i]].v=a[i];
	build(1,1,n);char op;int u,v;
	q=gi();while (q--){
		op=getchar();while (op!='C'&&op!='Q') op=getchar();
		if (op=='C') u=gi(),v=gi(),work(u,v);
		else u=gi(),printf("%lld\n",query(1,1,n,dfn[u],End[top[u]]).v);
	}
	return 0;
}
posted @ 2018-06-30 17:20  租酥雨  阅读(304)  评论(0编辑  收藏  举报