【bzoj4372】 烁烁的游戏【动态树分治】

烁烁的游戏

Description

背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠。
题意:
给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠。
烁烁他每次会跳到一个节点u,把周围与他距离不超过d的节点各吸引出w只皮皮鼠。皮皮鼠会被烁烁吸引,所以会一直待在节点上不动。
烁烁很好奇,在当前时刻,节点u有多少个他的好朋友—皮皮鼠。
大意:
给一颗n个节点的树,边权均为1,初始点权均为0,m次操作:
Q x:询问x的点权。
M x d w:将树上与节点x距离不超过d的节点的点权均加上w。

Input

第一行两个正整数:n,m
接下来的n-1行,每行三个正整数u,v,代表u,v之间有一条边。
接下来的m行,每行给出上述两种操作中的一种。

Output

对于每个Q操作,输出当前x节点的皮皮鼠数量。

解法:

先考虑暴力。对于每一次查询,把整棵树遍历一遍,对于每个遍历到的节点,加上当前节点到查询点的距离的所有的修改值。最后得到的就是这个点的点权。
有了思路,就上树分治啦!每个点开2棵可区间修改的李超线段树。
第一棵线段树: 以子树中的每一个点到自己的距离为下标,维护每一种距离的修改值。
第二棵线段树: 以子树中的每一个点到自己的父亲的距离为下标,维护每一种距离的修改值。
修改: 设要修改的点为u,要与u距离不超过d1的所有点加上w。在向上爬的过程中,设当前爬到节点为i,u到fa[i]的距离为d2。则将fa[i]的第一棵线段树下标为0 ~ d1-d2都加上w,再将用于去掉重复的i的第二棵线段树下标为0 ~ d1-d2都加上w。
查询: 其实统计答案的原理和暴力是一样的。在向上爬的过程中,设当前点的父亲到查询点的距离为d。对于每个祖先节点,答案加上这个点的父亲的第一棵线段树下标为d的修改值,再减去这个点的第二棵线段树下标为d的修改值,目的仍然是减去重复的。
代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m,u,v,d,cnt,head[N],to[N*2],nxt[N*2];
int idx,lg2[N*2],dfn[N],dep[N],siz[N],pos[N*2],f[N*2][20];
int mi,size,rt,fa[N];
int tot,root[N][2],tag[20000005],ch[20000005][2];
bool vis[N];
char s[5];
void adde(int u,int v){
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}
void dfs(int pre,int u){
	dfn[u]=++idx;
	pos[idx]=u;
	int v;
	for(int i=head[u];i;i=nxt[i]){
		v=to[i];
		if(v!=pre){
			dep[v]=dep[u]+1;
			dfs(u,v);
			pos[++idx]=u;
		}
	}
}
void st(){
	for(int i=1;i<=idx;i++){
		f[i][0]=pos[i];
	}
	for(int j=1;j<=20;j++){
		for(int i=1;i+(1<<j)-1<=idx;i++){
			if(dep[f[i][j-1]]<dep[f[i+(1<<(j-1))][j-1]]){
				f[i][j]=f[i][j-1];
			}else{
				f[i][j]=f[i+(1<<(j-1))][j-1];
			}
		}
	}
}
int lca(int u,int v){
	if(dfn[u]>dfn[v]){
		swap(u,v);
	}
	int k=lg2[dfn[v]-dfn[u]];
	if(dep[f[dfn[u]][k]]<dep[f[dfn[v]-(1<<k)+1][k]]){
		return f[dfn[u]][k];
	}else{
		return f[dfn[v]-(1<<k)+1][k];
	}
}
void dfsroot(int pre,int u){
	siz[u]=1;
	int v,mx=0;
	for(int i=head[u];i;i=nxt[i]){
		v=to[i];
		if(v!=pre&&!vis[v]){
			dfsroot(u,v);
			siz[u]+=siz[v];
			mx=max(mx,siz[v]);
		}
	}
	mx=max(mx,size-siz[u]);
	if(mx<mi){
		mi=mx;
		rt=u;
	}
}
int dis(int u,int v){
	return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void build(int pre,int u){
	vis[u]=true;
	fa[u]=pre;
	int v;
	for(int i=head[u];i;i=nxt[i]){
		v=to[i];
		if(!vis[v]){
			mi=size=siz[v];
			dfsroot(u,v);
			build(u,rt);
		}
	}
}
void upd(int &o,int l,int r,int L,int R,int v){
	if(!o){
		o=++tot;
	}
	if(L==l&&R==r){
		tag[o]+=v;
		return;
	}
	int mid=(l+r)/2;
	if(R<=mid){
		upd(ch[o][0],l,mid,L,R,v);
	}else if(L>mid){
		upd(ch[o][1],mid+1,r,L,R,v);
	}else{
		upd(ch[o][0],l,mid,L,mid,v);
		upd(ch[o][1],mid+1,r,mid+1,R,v);
	}
}
int qry(int o,int l,int r,int k){
	if(!o){
		return 0;
	}
	if(l==r){
		return tag[o];
	}
	int mid=(l+r)/2;
	if(k<=mid){
		return tag[o]+qry(ch[o][0],l,mid,k);
	}{
		return tag[o]+qry(ch[o][1],mid+1,r,k);
	}
}
int query(int u){
	int ret=qry(root[u][0],0,n,0),tmp;
	for(int i=u;fa[i];i=fa[i]){
		tmp=dis(fa[i],u);
		ret+=qry(root[fa[i]][0],0,n,tmp)-qry(root[i][1],0,n,tmp);
	}
	return ret;
}
void update(int u,int d,int w){
	upd(root[u][0],0,n,0,d,w);
	int tmp;
	for(int i=u;fa[i];i=fa[i]){
		tmp=dis(fa[i],u);
		if(tmp>d){
			continue;
		}
		upd(root[fa[i]][0],0,n,0,d-tmp,w);
		upd(root[i][1],0,n,0,d-tmp,w);
	}
}
int main(){
	for(int i=2;i<=200000;i++){
		lg2[i]=lg2[i/2]+1;
	}
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		scanf("%d%d",&u,&v);
		adde(u,v);
		adde(v,u);
	}
	dfs(0,1);
	st();
	mi=size=n;
	dfsroot(0,1);
	build(0,rt);
	for(int i=1;i<=m;i++){
		scanf("%s%d",s,&u);
		if(s[0]=='Q'){
			printf("%d\n",query(u));              
		}else{
			scanf("%d%d",&d,&v);
			update(u,d,v);                                             
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值