BZOJ4372烁烁的游戏——动态点分治+线段树(点分树套线段树)
题目描述
背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠。
题意:
给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠。
烁烁他每次会跳到一个节点u,把周围与他距离不超过d的节点各吸引出w只皮皮鼠。皮皮鼠会被烁烁吸引,所以会一直待在节点上不动。
烁烁很好奇,在当前时刻,节点u有多少个他的好朋友---皮皮鼠。
大意:
给一颗n个节点的树,边权均为1,初始点权均为0,m次操作:
Q x:询问x的点权。
M x d w:将树上与节点x距离不超过d的节点的点权均加上w。
输入
第一行两个正整数:n,m
接下来的n-1行,每行三个正整数u,v,代表u,v之间有一条边。
接下来的m行,每行给出上述两种操作中的一种。
输出
对于每个Q操作,输出当前x节点的皮皮鼠数量。
样例输入
7 6
1 2
1 4
1 5
2 3
2 7
5 6
M 1 1 2
Q 5
M 2 2 3
Q 3
M 1 2 1
Q 2
1 2
1 4
1 5
2 3
2 7
5 6
M 1 1 2
Q 5
M 2 2 3
Q 3
M 1 2 1
Q 2
样例输出
2
3
6
3
6
提示
数据范围:
n,m<=10^5,|w|<=10^4
注意:w不一定为正整数,因为烁烁可能把皮皮鼠吓傻了。
这道题和BZOJ3730很像,重点在于修改。
同样考虑对于单次修改用动态点分治如何修改。
假设修改点为x,增加值为k。
对于分治联通块包含x的分治中心,设这个分治中心距离x为d。
那么需要将这个联通块中与分治中心距离<=d的点的点权都增加k,但与x位于分治中心同一子树中的点这样算不合法且在递归下一层分治中心时会对它们进行修改,所以将这些点的点权都减少k(与上面的增加k抵消)。
因为与分治中心距离相同的点会被同时修改,所以每个点开一棵线段树维护以这个点为分治中心时,联通块中与它距离为各个值的点的点权增加多少。
还要开一棵线段树维护以这个点为分治中心时,联通块中与它在点分树上父节点距离为各个值的点点权减少多少。
修改和查询时都从操作点往根爬并对沿途点修改或统计答案。
但这样是前缀修改、单点查询,对于动态开点线段树不好写,我们差分一下就变成单点修改、后缀查询。
时间复杂度O(nlogn^2)
#include<map> #include<set> #include<queue> #include<stack> #include<cmath> #include<vector> #include<bitset> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define mid ((l+r)>>1) using namespace std; int n,m; int dfn; int num; int tot; int cnt; int rot; int sum; int x,y,z; char ch[3]; int f[100010]; int d[100010]; int s[100010]; int mn[100010]; int lg[200010]; int to[200010]; int mx[100010]; int dep[100010]; int vis[100010]; int head[100010]; int root[100010]; int next[200010]; int size[100010]; int froot[100010]; int g[18][200010]; struct miku { int ls,rs,sum; }tr[10000000]; inline void add(int x,int y) { next[++tot]=head[x]; head[x]=tot; to[tot]=y; } void dfs(int x,int fa) { g[0][++dfn]=dep[x]; s[x]=dfn; for(int i=head[x];i;i=next[i]) { if(to[i]!=fa) { dep[to[i]]=dep[x]+1; dfs(to[i],x); g[0][++dfn]=dep[x]; } } } inline void getroot(int x,int fa) { size[x]=1; mx[x]=0; for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]&&to[i]!=fa) { getroot(to[i],x); size[x]+=size[to[i]]; mx[x]=max(mx[x],size[to[i]]); } } mx[x]=max(mx[x],num-size[x]); if(mx[x]<mx[rot]) { rot=x; } } void find_dep(int x,int fa) { d[x]=d[fa]+1; sum=max(sum,d[x]); for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]&&to[i]!=fa) { find_dep(to[i],x); } } } inline void partation(int x) { vis[x]=1; sum=0; find_dep(x,0); mn[x]=sum; for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]) { num=size[to[i]]; rot=0; getroot(to[i],0); f[rot]=x; partation(rot); } } } inline int lca(int x,int y) { x=s[x]; y=s[y]; if(x>y) { swap(x,y); } int len=lg[y-x+1]; return min(g[len][x],g[len][y-(1<<len)+1]); } inline int dis(int x,int y) { return dep[x]+dep[y]-(lca(x,y)<<1); } inline void insert(int &rt,int l,int r,int k,int x) { if(!rt) { rt=++cnt; } tr[rt].sum+=x; if(l==r) { return ; } if(k<=mid) { insert(tr[rt].ls,l,mid,k,x); } else { insert(tr[rt].rs,mid+1,r,k,x); } } inline int query(int rt,int l,int r,int k) { if(!rt||k>r) { return 0; } if(l==r) { return tr[rt].sum; } if(k<=mid) { return tr[tr[rt].rs].sum+query(tr[rt].ls,l,mid,k); } else { return query(tr[rt].rs,mid+1,r,k); } } inline void change(int x,int k,int val) { for(int i=x;i;i=f[i]) { if(dis(x,i)<=k) { insert(root[i],0,mn[i],k-dis(x,i),val); } if(f[i]&&dis(x,f[i])<=k) { insert(froot[i],0,mn[f[i]],k-dis(x,f[i]),val); } } } inline int query(int x) { int res=0; for(int i=x;i;i=f[i]) { res+=query(root[i],0,mn[i],dis(x,i)); if(f[i]) { res-=query(froot[i],0,mn[f[i]],dis(x,f[i])); } } return res; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs(1,0); for(int i=2;i<=dfn;i++) { lg[i]=lg[i>>1]+1; } for(int j=1;(1<<j)<=dfn;j++) { for(int i=1;i+(1<<j)-1<=dfn;i++) { g[j][i]=min(g[j-1][i],g[j-1][i+(1<<(j-1))]); } } mx[0]=1<<30; num=n; getroot(1,0); partation(rot); while(m--) { scanf("%s",ch); if(ch[0]=='M') { scanf("%d%d%d",&x,&y,&z); change(x,y,z); } else { scanf("%d",&x); printf("%d\n",query(x)); } } }