【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;
}