P6329 模板点分树 | 震波

Posted hn-wrp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P6329 模板点分树 | 震波相关的知识,希望对你有一定的参考价值。

前言

由于这篇题解思路并没有什么区别,所以这篇题解的意义在于稍稍更细致地讲下思路和卡常方法。估计也只有我常数这么大了

思路

第一感

由于题目要查询到一个点距离为(k)以内的所有点的权值和,一个显然的想法就是对每个点开一个线段树维护权值和,下标维护距离,然后暴力查询。显然这是(MLE+TLE)的。考虑优化这个过程。

爆空间?

首先(MLE)的解决很简单,动态开点就行了,于是问题在于(TLE)

超时间?

考虑之前(TLE)是因为每次修改点权的时候我们都要对每个点去修改。这表明我们这样子的维护方式是不行的。所以考虑把所有点给答案的贡献分为两个部分。一个常用的方法是分为在子树内和在子树外两个部分。但是由于树高不确定,这导致我们的复杂度可能退化。所以考虑点分树来保证复杂度。

点分树

考虑一组((x,k)),对于(x)在点分树上的子树的贡献我们可以开一棵动态开点的线段树按之前的方法维护。设这个值为(sm1(x,p))。然后我们考虑子树外的贡献,也就是我们跳到点分树上的父亲结点时需要计算的贡献。如果我们直接计算(sm1(fa[x],k-dis(x,fa[x])))很显然我们会把在(x)子树内的点算重复。所以我们还要开一棵线段树记下(x)的子树内到(fa[x])的权值和,记为(sm2(x,p)),那么每次向上跳的时候获得的答案就是(sm1(i,k-dis(x,i))-sm2(lst,k-dis(x,i)))其中(i)是当前跳到的祖先,(lst)是上一个跳到的祖先。那么这样的时间复杂度就是(nlog_2^2n)的。

卡常&实现细节

  • 动态开点的线段树第一棵在(x)这个节点的最大深度开到(siz[x])就行了,第二棵要开到(siz[fa[x]])
  • 线段树查询的时候查到空节点直接返回(0),不要像我这个蒟蒻一样还去开节点啊....
  • 最好使用(RMQ)(LCA)然鹅我并没有用
  • 使用(namespace)让你毫无顾虑的使用重复数组名。
  • 把初始的值看做修改可以让你省很多事。

代码

由于我用的树剖,所以常数可能还是大了点,不开(O_2)过不了。。。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define R register
#define LL long long
const int inf=0x3f3f3f3f;
const int MAXN=1e5+10;

inline int read() {
	char a=getchar(); int x=0,f=1;
	for(;a>‘9‘||a<‘0‘;a=getchar()) if(a==‘-‘) f=-1;
	for(;a>=‘0‘&&a<=‘9‘;a=getchar()) x=x*10+a-‘0‘;
	return x*f;
}

namespace Data {
	int tot=0;
	int sum[MAXN*50],ls[MAXN*50],rs[MAXN*50];
	class SegTree {
		private:
			int rt;
		public:
			SegTree() { rt=0; }
			inline void add(int &x,int l,int r,int ad,int k) {
				if(!x) 	x=++tot;
				sum[x]+=k;	
				if(l==r) return;
				int mid=l+r;mid>>=1;
				if(ad<=mid) add(ls[x],l,mid,ad,k);
				else add(rs[x],mid+1,r,ad,k);
			}
			inline int ask(int &x,int l,int r,int Le,int Ri) {
				if(!x) return 0;
				if(l>=Le&&r<=Ri) return sum[x];
				int mid=l+r;mid>>=1;
				int res=0;
				if(Le<=mid)  res+=ask(ls[x],l,mid,Le,Ri);
				if(Ri>mid) res+=ask(rs[x],mid+1,r,Le,Ri);
				return res;
			}
			inline void Add(int n,int pos,int k) { add(rt,0,n,pos,k); }
			inline int Ask(int n,int l,int r) { return ask(rt,0,n,l,r); }
	};
}
using Data::SegTree;

int n,m;
int val[MAXN];
struct edge { int to,next; } e[MAXN<<1];
int cnt,head[MAXN];
inline void add(int x,int y) { e[++cnt]={ y,head[x] }; head[x]=cnt; }

namespace Graph {
	int dep[MAXN],fa[MAXN],top[MAXN],siz[MAXN],son[MAXN];
	inline void dfs1(int x,int fx) {
		fa[x]=fx; dep[x]=dep[fx]+1; siz[x]=1;
		for(R int i=head[x];i;i=e[i].next) {
			int y=e[i].to; if(y==fx) continue;
			dfs1(y,x); siz[x]+=siz[y];
			if(siz[y]>siz[son[x]]) son[x]=y;
		}
	}
	inline void dfs2(int x,int topx) {
		top[x]=topx;
		if(son[x]) dfs2(son[x],topx);
		for(R int i=head[x];i;i=e[i].next) {
			int y=e[i].to; if(y==fa[x]||y==son[x]) continue;
			dfs2(y,y);
		}
	}
	inline int LCA(int x,int y) {
		while(top[x]!=top[y]) 
			if(dep[top[x]]>dep[top[y]]) x=fa[top[x]];
			else y=fa[top[y]];
		return dep[x]<dep[y]?x:y;
	}
	inline int dis(int x,int y) {
		return dep[x]+dep[y]-dep[LCA(x,y)]*2;
	}
	inline void build() {
		dfs1(1,0); dfs2(1,1);
	}
}

namespace Tree {
	int rt,total;
	int mx[MAXN],fa[MAXN],siz[MAXN],vis[MAXN];
	SegTree sm1[MAXN],sm2[MAXN];
	inline void getrt(int x,int fx) {
		siz[x]=1; mx[x]=0;
		for(R int i=head[x];i;i=e[i].next) {
			int y=e[i].to;
			if(y==fx||vis[y]) continue;
			getrt(y,x); siz[x]+=siz[y];
			mx[x]=max(mx[x],siz[y]);
		}
		mx[x]=max(mx[x],total-siz[x]);
		if(mx[x]<mx[rt]) rt=x;
	}
	inline void solve(int x) {
		vis[x]=1;
		for(R int i=head[x];i;i=e[i].next) {
			int y=e[i].to;
			if(vis[y]) continue;
			total=siz[y]; rt=0; getrt(y,0);
			fa[rt]=x; solve(rt);
		}
	}
	inline void build() {
		total=n; mx[0]=inf; getrt(1,0);
		getrt(rt,0); solve(rt);
	}
	inline void update(int x,int v) {
		for(R int i=x;i;i=fa[i]) {
			sm1[i].Add(Graph::siz[i],Graph::dis(x,i),v);
			if(fa[i])
				sm2[i].Add(Graph::siz[fa[i]],Graph::dis(x,fa[i]),v);
		}
	}
	inline int ask(int x,int k) {
		int res=0;
		int lst=0;
		for(R int i=x;i;i=fa[i]) {
			int d=Graph::dis(x,i);
			if(d>k) { lst=i; continue; }
			res+=sm1[i].Ask(Graph::siz[i],0,k-d);
			if(lst) res-=sm2[lst].Ask(Graph::siz[i],0,k-d);
			lst=i;
		}
		return res;
	}
};

inline void Init() {
	n=read(); m=read();
	for(R int i=1;i<=n;i++) val[i]=read();
	for(R int i=1;i<n;i++) {
		int x=read(), y=read();
		add(x,y); add(y,x);
	}
}

inline void Solve() {
	for(R int i=1;i<=n;i++) Tree::update(i,val[i]);
	int las=0;
	while(m--) {
		int op=read(),x=read(),y=read();
		x^=las; y^=las;
		if(op==0) printf("%d
",las=Tree::ask(x,y));
		else Tree::update(x,y-val[x]),val[x]=y;
	}
}

int main() {
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	Init();
	Graph::build(); Tree::build();
	Solve();
	return 0;	
}

以上是关于P6329 模板点分树 | 震波的主要内容,如果未能解决你的问题,请参考以下文章

点分树BZOJ3730 震波

[BZOJ3730]震波

Bzoj3730: 震波

BZOJ3730震波(动态点分治)

bzoj3730:震波

bzoj2117 [ 2010国家集训队 ] -- 点分树+二分答案