【BZOJ3730】震波(点分树)
大致题意: 给你一棵树,每个节点有一个权值,需要实现两种操作:求与节点\(x\)距离不超过\(k\)的所有点的权值和;单点修改。(强制在线)
前言
我还是太弱了。。。
先想这道题各种复杂的细节想了半天,总共码了一个多小时。
然后,交上去又是\(RE\)又是\(TLE\),看到这是强制在线的题目,自然而然以为自己\(WA\)了。
先去吃了个午饭,接着心态爆炸地调了半个小时之后——
原来,我是真的\(RE\)了。。。(不用说,又是一个智障错误)
点分树
什么是点分树?
我在这篇博客里有过简单的提及:再学点分治——动态点分治。
现在,相当于是重拾动态点分治了吧。
大致想法
考虑对于每个点,我们记下它在点分树上所有祖先以及到每一个祖先的距离(一个节点最多只有\(logN\)个祖先)。
然后,我们开两个树状数组,一个维护子树内到其距离小于等于\(k\)的元素和,一个维护在它子树内的节点对父节点的贡献。(注意,这里需要开\(vector\),否则就会爆内存。)
为什么要这么复杂呢?因为点分树并不是真正的树,一个点到父节点的父节点的距离,甚至可能小于它到父节点的距离,所以不这样维护一些就会变得超级麻烦。
对于修改,我们只要修改其到根的路径上所有节点的两个树状数组。
对于询问,要注意不能重复,即计算父节点答案时要减去子节点子树内的贡献,这也正是开两个树状数组的原因。
大致想法就是这么简单,只不过写起来要麻烦多了而已。。。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define pb push_back
using namespace std;
int n,a[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class TreeArray//树状数组
{
private:
int n;vector<int> a;//注意开vector
public:
I void Set(CI x) {n=x,a.resize(x+1);}I void U(RI x,CI v) {W(x<=n) a[x]+=v,x+=x&-x;}
I int Q(RI x,RI t=0) {x>n&&(x=n);W(x) t+=a[x],x-=x&-x;return t;}
};
class DotSolveTree//点分树
{
private:
int Rt,Sz[N+5],Mx[N+5],used[N+5],dis[N+5],T,S[N+5],P[N+5];
TreeArray V[N+5],G[N+5];vector<int> f[N+5],d[N+5];
I void GetRt(CI x,RI s,CI lst=0)//找重心
{
Sz[x]=1,Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&
e[i].to^lst&&(GetRt(e[i].to,s,x),Sz[x]+=Sz[e[i].to],Gmax(Mx[x],Sz[e[i].to]));
Gmax(Mx[x],s-Sz[x]),Mx[x]<Mx[Rt]&&(Rt=x);
}
I int GetDis(CI x,CI lst=0,CI d=0)//求出到当前点最远的距离
{
RI i,t,Mx=d;for(i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&
e[i].to^lst&&(dis[e[i].to]=d+1,t=GetDis(e[i].to,x,d+1),Gmax(Mx,t));
return Mx;
}
I void dfs(CI x,CI lst=0)//遍历路径记录信息
{
++T,S[T]=dis[P[T]=x];for(RI i=lnk[x];i;i=e[i].nxt)
!used[e[i].to]&&e[i].to^lst&&(dfs(e[i].to,x),0);
}
I void Work(RI x)
{
RI i,Mx=GetDis(x);V[x].Set(Mx);
for(used[x]=1,i=lnk[x];i;i=e[i].nxt) if(!used[e[i].to])
{
Rt=0,GetRt(e[i].to,Sz[e[i].to]),dfs(Rt),G[Rt].Set(Mx);
W(T) V[x].U(S[T],a[P[T]]),G[Rt].U(S[T],a[P[T]]),f[P[T]].pb(x),d[P[T]].pb(S[T]),--T;//计算贡献
Work(Rt);//继续处理子树,注意这句话要放后面
}
}
public:
I void Build()//建树
{
Mx[Rt=0]=1e9,GetRt(1,n),Work(Rt);
for(RI i=1;i<=n;++i) reverse(f[i].begin(),f[i].end()),reverse(d[i].begin(),d[i].end());//注意这两个数组存下来的顺序是反的,故应翻转
}
I void Upt(CI x,CI v)//修改
{
if(!f[x].size()) return;//注意这句话,就是因为没有它,害得我RE了
G[x].U(d[x][0],v);for(RI i=0,sz=f[x].size();i^sz;++i)
V[f[x][i]].U(d[x][i],v),i^(sz-1)&&(G[f[x][i]].U(d[x][i+1],v),0);
}
I int Qry(CI x,CI k)//询问
{
RI t=a[x]+V[x].Q(k);for(RI i=0,sz=f[x].size();i^sz;++i) d[x][i]<=k&&
(t+=a[f[x][i]]+V[f[x][i]].Q(k-d[x][i])-G[i?f[x][i-1]:x].Q(k-d[x][i]));//此处应防止算重
return t;
}
}T;
int main()
{
RI Qt,i,op,x,y,lst=0;for(F.read(n),F.read(Qt),i=1;i<=n;++i) F.read(a[i]);
for(i=1;i^n;++i) F.read(x),F.read(y),add(x,y),add(y,x);T.Build();
W(Qt--) F.read(op),F.read(x),F.read(y),x^=lst,y^=lst,
op?(T.Upt(x,y-a[x]),a[x]=y):(F.writeln(lst=T.Qry(x,y)),0);
return F.clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒