【BZOJ4712】洪水(动态DP)(链分治)(线段树)

25 篇文章 0 订阅
6 篇文章 0 订阅
题目描述

给出一棵树,点有点权。多次增加某个点的点权,并在某一棵子树中询问:选出若干个节点,使得每个叶子节点到根节点的路径上至少有一个节点被选择,求选出的点的点权和的最小值。

输入

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

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

输出

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

样例输入

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

样例输出

3
1
4


解析:

在猫锟论文上看见的题,来写一写。

显然每次 O ( n ) O(n) O(n)的DP是很好想且会超时的。

f [ u ] f[u] f[u]表示将在 u u u子树中的叶子全部覆盖需要花费的代价, c h ( u ) ch(u) ch(u) u u u的儿子集合,则我们有如下转移:

f [ u ] = min ⁡ ( a [ u ] , ∑ v ∈ c h ( u ) f [ v ] ) f[u]=\min(a[u],\sum_{v\in ch(u)}f[v]) f[u]=min(a[u],vch(u)f[v])

考虑将轻重儿子分开,令 C h ( u ) Ch(u) Ch(u)表示 u u u的轻儿子集合, s s s表示 u u u的重儿子。

发现这个玩意已经可以链分治了。

g [ u ] = ∑ v ∈ C h ( u ) f [ v ] g[u]=\sum_{v\in Ch(u)}f[v] g[u]=vCh(u)f[v],我们直接维护所有点的 g [ u ] , a [ u ] g[u],a[u] g[u],a[u],记为二元组 ( g , a ) (g,a) (g,a)

现在我们发现每次计算其实只需要从当前点的对应重链底向上一路DP就行了。

然而这个DP其实上只是维护一个前缀和的最小值。。。直接上线段树就行了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline char getalpha(){
		re char c;
		while(!isalpha(c=gc()));return c;
	}
	
	inline ll getint(){
		re char c;
		while(!isdigit(c=gc()));re ll num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=2e5+5;

int n,m;

int last[N],nxt[N<<1],to[N<<1],ecnt;
inline void addedge(int u,int v){
	nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v;
	nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u;
}

int fa[N],bot[N],top[N],siz[N],son[N];
int in[N],pos[N],dfs_clock;
void dfs1(int u){
	siz[u]=1;
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]])if(v!=fa[u]){
		fa[v]=u;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}

ll g[N],f[N],a[N];
void dfs2(int u){
	pos[in[u]=++dfs_clock]=u;
	bot[u]=u;f[u]=a[u];
	if(son[u]){
		top[son[u]]=top[u];
		dfs2(son[u]);
		bot[u]=bot[son[u]];
	}
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]])if(v!=fa[u]&&v!=son[u]){
		top[v]=v;
		dfs2(v);
		g[u]+=f[v];
	}
	if(son[u])f[u]=std::min(f[u],g[u]+f[son[u]]);
}

struct data{
	ll sum,ls;
	data(){}
	data(cs ll &_sum,cs ll &_ls):sum(_sum),ls(_ls){}
	friend data operator+(cs data &a,cs data &b){
		return data(a.sum+b.sum,std::min(a.sum+b.ls,a.ls));
	}
}val[N<<2];

inline void pushup(int k){val[k]=val[k<<1]+val[k<<1|1];}

inline void build(int k,int l,int r){
	if(l==r){
		val[k]=data(g[pos[l]],a[pos[l]]);
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}

inline void updatev(int k,int l,int r,cs int &pos,cs ll &delta){
	if(l==r){
		val[k].ls+=delta;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)updatev(k<<1,l,mid,pos,delta);
	else updatev(k<<1|1,mid+1,r,pos,delta);
	pushup(k);
}

inline void updateg(int k,int l,int r,cs int &pos,cs ll &delta){
	if(l==r){
		val[k].sum+=delta;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)updateg(k<<1,l,mid,pos,delta);
	else updateg(k<<1|1,mid+1,r,pos,delta);
	pushup(k);
}

inline data query(int k,int l,int r,cs int &ql,cs int &qr){
	if(ql<=l&&r<=qr)return val[k];
	int mid=(l+r)>>1;
	if(qr<=mid)return query(k<<1,l,mid,ql,qr);
	if(mid<ql)return query(k<<1|1,mid+1,r,ql,qr);
	return query(k<<1,l,mid,ql,qr)+query(k<<1|1,mid+1,r,ql,qr);
}

inline void update(int u,ll vl){
	int v=u;
	ll t;
	while(u){
		if(vl==0)return ;
		t=query(1,1,n,in[top[u]],in[bot[u]]).ls;
		if(v==u)updatev(1,1,n,in[u],vl);
		else updateg(1,1,n,in[u],vl);
		vl=query(1,1,n,in[top[u]],in[bot[u]]).ls-t;
		u=fa[top[u]];
	}
}

signed main(){
	n=getint();
	for(int re i=1;i<=n;++i)a[i]=getint();
	for(int re i=1;i<n;++i)addedge(getint(),getint());
	dfs1(1),top[1]=1,dfs2(1);
	build(1,1,n);
	m=getint();
	while(m--)switch(getalpha()){
		case 'Q':{
			int u=getint();
			cout<<query(1,1,n,in[u],in[bot[u]]).ls<<"\n";
			break;
		}
		case 'C':{
			int u=getint();ll val=getint();
			update(u,val);
			break;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值