其他分享
首页 > 其他分享> > 【BZOJ3730】震波(点分树)

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

标签:Sz,CI,Mx,点分树,震波,BZOJ3730,RI,节点,define
来源: https://www.cnblogs.com/chenxiaoran666/p/BZOJ3730.html