【BZOJ4712】洪水

Description

  
  ​ 原题链接(权限题)
  
​   题目大意:给一棵带点权的树,要求支持两种操作:
  
​   (1)给某一个点的权值加上\(x\)
  
​   (2)令堵上一个点的代价为其权值,询问以某一个点\(u\)为根的子树中,至少花费多少代价,使得\(u\)与子树中的叶子节点不连通(不算\(u\),可以赌叶子节点和\(u\)本身)
  
​   点数\(n \le 200000\)
  
  
  

Solution

  
​   静态DP很好做:记\(u\)的点权为\(w_u\),设\(f_u\)表示\(u\)子树的答案,则有

\[f_u=\min\{w_u,\sum_vf_v\} \]

​   现在要支持点权修改,怎么实现?这种题貌似叫动态DP,可以用树剖的套路来解决。
  
​   令\(son_u\)表示\(u\)的重儿子,我们把重儿子的权值分离出来:

\[f_u=\min\{w_u,f_{son_u}+\sum_{v \ne son_u}f_v\} \]

​   令后面一个和式为\(g_u\),则有

\[f_u=\min\{w_u,g_u+f_{son_u}\} \]

​   使用树链剖分维护两个值:每个点\(u\)\(w_u\)\(g_u\)
  
​   接下来,在此表示法的意义下,我们考虑询问\(u\)时,其答案\(f_u\)具体怎么体现。根据套路,我们要尽可能用一种线段树能维护的问题形式表现答案。我们发现,\(f_u\)所含的比较的元素可以展开来看(其中\(v_i\)表示\(u\)\(i\)级重儿子):

\[\min\{w_u,g_u+w_{v_1},g_u+g_{v_1}+w_{v_2},g_u+g_{v_1}+g_{v_2}+w_{v_3},...\} \]

  ​ 这其实就是一个最小前缀和的形式,只不过前缀和的最后一个元素是该位置的\(w\),其余元素是\(g\)。这其实很好实现,用线段树来维护每一条重链的最小前缀和。具体实现上,两两重链之间插入一个特殊点,其权值设为正无穷,以限制考虑的前缀不可在链之间跨越。
  
​   所以,\(w\)\(g\)是真正记录在树剖中的数据,而每个点\(u\)\(f_u\),则是体现为从\(u\)点走重链向下的最小前缀和。
  
​   修改上,也是这类问题的一贯做法。首先单独修改\(u\)的权值\(w_u\)。根据定义,其他点的\(w\)一定不会受到影响。当下唯一影响的,就是\(u\)所在链链顶元素的\(g\)值。我们计算出于\(u\)修改\(w_u\)后与修改前,链顶\(f\)值的变化量。这个变化量只会影响到链顶的父亲——另一条重链上的某一个点的\(g\)值,在线段树上修改。我们发现,这时候我们的操作模式已经和“单独修改\(u\)的权值\(w_u\)”一模一样了,只不过初始时我们修改\(w_u\)通过线段树上推的方式间接影响全局,而现在我们通过直接修改\(g_u\)的方式直接影响全局。因此计算出该点修改前后,其链顶的\(f\)的差值,继续向上重复这一过程即可。
  
​   可以发现,这一过程发生的次数,就是从初始点往根节点走遇到的轻边数量。由于每一个点到根节点经过的轻链条数为\(\mathcal O(\log n)\),因此单次修改的复杂度是\(\mathcal O(\log ^2 n)\)
  
  
  
​   在做这题之前,我已经做过了类似的一道动态DP题,并且很快地想到了将重儿子的数据单独分离的套路。可是我在探究答案究竟怎么体现的时候卡住了,没有发现“最小前缀和”的形式,所以没有写出来。
  
  
  

Code

  

#include <cstdio>
using namespace std;
typedef long long ll;
const int N=400005;
const ll INF=1LL<<30;
int n;
ll w[N],f[N],g[N];
int h[N],tot;
struct Edge{
	int v,next;
}e[N*2];
int pre[N],dep[N],size[N],son[N],who[N],loc[N],locnt,top[N];
inline ll min(ll x,ll y){
	return x<y?x:y;
}
inline void addEdge(int u,int v){
	e[++tot]=(Edge){v,h[u]}; h[u]=tot;
	e[++tot]=(Edge){u,h[v]}; h[v]=tot;
}
void readData(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%lld",w+i);
	int u,v;
	for(int i=1;i<n;i++){
		scanf("%d%d",&u,&v);
		addEdge(u,v);
	}
}
void d_dfs1(int u,int fa){
	pre[u]=fa;
	dep[u]=dep[fa]+1;
	son[u]=0;
	size[u]=1;
	g[u]=0;
	bool have=false;
	for(int i=h[u],v;i;i=e[i].next)
		if((v=e[i].v)!=fa){
			have=true;
			d_dfs1(v,u);
			size[u]+=size[v];
			if(!son[u]||size[v]>size[son[u]])
				son[u]=v;
			g[u]+=f[v];
		}
	if(!have) 
		f[u]=w[u], g[u]=INF;
	else 
		f[u]=min(w[u],g[u]);
}
void d_dfs2(int u,int _top){
	top[u]=_top;
	loc[u]=++locnt;
	who[locnt]=u;
	if(!son[u]){
		who[++locnt]=0;
		return;
	}
	g[u]-=f[son[u]];
	d_dfs2(son[u],_top);
	for(int i=h[u],v;i;i=e[i].next)
		if((v=e[i].v)!=pre[u]&&v!=son[u])
			d_dfs2(v,v);
}
namespace SEG{/*{{{*/
	const int S=N*2;
	int rt,sz;
	int ch[S][2];
	ll sum[S],best[S];
	void pushup(int u){
		sum[u]=sum[ch[u][0]]+sum[ch[u][1]];
		best[u]=min(best[ch[u][0]],sum[ch[u][0]]+best[ch[u][1]]);
	}
	void build(int &u,int l,int r){
		u=++sz;
		if(l==r){
			int x=who[l];
			sum[u]=g[x];
			best[u]=w[x];
			return;
		}
		int mid=(l+r)>>1;
		build(ch[u][0],l,mid);
		build(ch[u][1],mid+1,r);
		pushup(u);
	}
	void modify(int u,int l,int r,int pos,ll delta){
		if(l==r){
			int x=who[l];
			best[u]=w[x];
			sum[u]+=delta;
			return;
		}
		int mid=(l+r)>>1;
		if(pos<=mid)
			modify(ch[u][0],l,mid,pos,delta);
		else
			modify(ch[u][1],mid+1,r,pos,delta);
		pushup(u);
	}
	ll query(int u,int l,int r,int L,ll &lsum){
		if(L<=l){
			ll res=lsum+best[u];
			lsum+=sum[u];
			return res;
		}
		int mid=(l+r)>>1;
		ll res=INF;
		if(L<=mid) 
			res=min(res,query(ch[u][0],l,mid,L,lsum));
		res=min(res,query(ch[u][1],mid+1,r,L,lsum));
		return res;
	}
	ll queryBest(int l){
		ll lsum=0;
		return query(rt,1,locnt,l,lsum);
	}
}/*}}}*/
void initTree(){
	d_dfs1(1,0);
	d_dfs2(1,1);
	w[0]=INF;
	SEG::build(SEG::rt,1,locnt);
}
void modify(int u,ll x){
	ll delta=0,oldval,newval;
	w[u]+=x;
	for(;u;u=pre[top[u]]){
		oldval=SEG::queryBest(loc[top[u]]);
		SEG::modify(SEG::rt,1,locnt,loc[u],delta);
		newval=SEG::queryBest(loc[top[u]]);
		delta=newval-oldval;
		if(!delta) break;
	}
}
void answerQuery(){
	int q;
	char opt[2];
	ll x,y;
	scanf("%d",&q);
	while(q--){
		scanf("%s%lld",opt,&x);
		if(opt[0]=='Q')
			printf("%lld\n",SEG::queryBest(loc[x]));
		else{
			scanf("%lld",&y);
			modify(x,y);
		}
	}
}
int main(){
	readData();
	initTree();
	answerQuery();
	return 0;
}

posted @ 2018-08-21 20:18  RogerDTZ  阅读(188)  评论(0编辑  收藏  举报