ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【题解/学习笔记】点分树

2021-08-01 22:02:44  阅读:190  来源: 互联网

标签:笔记 int 题解 pos 原树 点分 点分树 维护 dis


点分树 | 震波

\(\text{Solution:}\)

是点分树的模板,这里讲一讲点分树。

本质就是把点分治的每一层分治重心给记录下来了,自然就形成了一棵树,并且树高是 \(O(\log n)\) 的。这很显然。

那么,考虑点分治的过程,实际上就是从点分树从根往下计算答案的过程了。

如果我们要计算点 \(x\) 的答案,对应地,应该是从根分治到点 \(x,\) 逆过来,就是从 \(x\) 跳到点分树的根。

所以,我们对答案维护只需要从这个点往上跳就好了。

那么我们考虑这个过程中哪些点对答案有贡献:

显然是跳到的点子树内距离根为 \(y-dis(x,now)\) 的点。因它们可以拼成一条长 \(y\) 的路径。

所以我们只需要对每个子树维护这个东西即可。一个是维护自己子树内部对它自己的贡献,另一个需要维护自己子树内单点对其父亲的贡献。

于是,应用容斥原理,计算其父亲子树内除 \(x\) 子树外其他点对它的贡献就是用总贡献减去 \(x\) 子树内对其父亲贡献为 \(y-dis\) 的部分。

而我们每次询问的是一个前缀和,所以可以树状数组维护。

由于点分树结构优秀,所以暴力跳的复杂度就是对的了。

同时,注意到点分树上的结构和原树的还是差别很大,所以我们需要在原树上面获得两点距离一类信息,而计算距离需要找 \(LCA,\) 利用欧拉序 \(st\) 表的科技就可以做到 \(O(n\log n)\) 预处理, \(O(1)\) 询问即可。

总之,点分树有以下特点:

  • 对应每个节点是点分治的分治重心

  • 树的结构非常均匀,树高是 \(O(\log n)\)

  • 树的结构与原树关系很小,很多信息需要在原树里面获得

  • 需要注意原树和点分树的差异,从而思考清楚两部分的信息处理

  • 然后考虑答案所求在点分树上如何统计,从而确定如何统计信息,需要维护哪些信息

  • 进而思考如何维护信息

大概就是这样的流程。至于修改操作:之所以建立点分树就是因为修改使得每次需要重复找重心进行计算,但这些修改对树结构没有影响,所以这些操作完全是不必要的,所以在点分树上修改就可以做到一个 \(\log n\) 复杂度

所以这些维护信息的数据结构往往还需要支持修改

如这题,就可以用树状数组来维护修改维护前缀和了。

至此,点分树介绍以及本题题解完全结束。

代码中附注释。

#include<bits/stdc++.h>
using namespace std;
const int inf=(1<<30);
const int N=2e5+10;
int n,m,tot,ans,rt,sum,minn,cnt,head[N];
inline int Min(int x,int y){return x<y?x:y;}
inline int Max(int x,int y){return x>y?x:y;}
int val[N],siz[N],dep[N],pa[N],pos[N],lg[N],st[N][21];
bool vis[N];
vector<int>C[2][N];
struct E{int nxt,to;}e[N<<1];
inline void add(int x,int y){e[++tot]=(E){head[x],y};head[x]=tot;}
void dfs1(int x,int fa){
	st[++cnt][0]=x;pos[x]=cnt;dep[x]=dep[fa]+1;
	for(int i=head[x];i;i=e[i].nxt){
		int j=e[i].to;
		if(j==fa)continue;
		dfs1(j,x);st[++cnt][0]=x;//原树的欧拉序 
	}
} 
inline int getmin(int x,int y){return dep[x]<dep[y]?x:y;}
void GetEuler(){
	for(int i=1;i<=cnt;++i)lg[i]=31-__builtin_clz(i);
	for(int t=1;(1<<t)<=cnt;++t){
		for(int i=1;i+(1<<t)<=cnt;++i){
			st[i][t]=getmin(st[i][t-1],st[i+(1<<(t-1))][t-1]);//维护关于欧拉序的st表 
		}
	}
}
inline int getdis(int u,int v){
	if(pos[u]>pos[v])swap(u,v);
	int pu=pos[u],pv=pos[v],len=pos[v]-pos[u]+1;
	//求两点第一次出现欧拉序中深度最小的节点 
	int L=getmin(st[pu][lg[len]],st[pv-(1<<lg[len])+1][lg[len]]);
	return dep[u]+dep[v]-(dep[L]<<1);//获得原树中两点距离 
}
inline int lowbit(int x){return x&(-x);}
void change(int u,int opt,int x,int v){
	x++;//注意树状数组下标不能是0 
	for(int i=x;i<=siz[u];i+=lowbit(i))C[opt][u][i]+=v;
}
int query(int u,int opt,int x){
	x++;
	int res=0;
	x=Min(x,siz[u]);
	for(int i=x;i;i-=lowbit(i))res+=C[opt][u][i];
	return res;
}
void Gr(int x,int fa){
	siz[x]=1;
	int res=0;
	for(int i=head[x];i;i=e[i].nxt){
		int j=e[i].to;
		if(j==fa||vis[j])continue;//避免走父亲以及之前的重心 
		Gr(j,x);siz[x]+=siz[j];res=Max(res,siz[j]);
	}
	res=Max(res,sum-siz[x]);
	if(res<minn)minn=res,rt=x;//从子树里面找重心 
}
void dfs(int u){
	vis[u]=1;siz[u]=sum+1;//vis表示是不是已经在点分树中 
	C[0][u].resize(siz[u]+1);
	C[1][u].resize(siz[u]+1); //提前定好其大小,最长就是链 
	for(int i=head[u];i;i=e[i].nxt){
		int j=e[i].to;
		if(vis[j])continue;
		sum=siz[j];rt=0;minn=inf;
		Gr(j,0);pa[rt]=u;dfs(rt);//找j的重心并与当前点分树点连边 
	}
}
void modify(int u,int w){
	for(int i=u;i;i=pa[i])change(i,0,getdis(u,i),w);//把子树内对x的贡献算上 
	for(int i=u;pa[i];i=pa[i])change(i,1,getdis(u,pa[i]),w);//计算子树内点对fa的贡献 
}
int opt;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)scanf("%d",&val[i]);
	for(int i=1;i<n;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs1(1,0);GetEuler();sum=n;minn=inf;
	Gr(1,0);dfs(rt);//找到 1 的重心,然后建立点分树 
	for(int i=1;i<=n;++i)modify(i,val[i]);//初始化,dis啥的在modify里 
	for(;m;m--){
		int x,y;
		scanf("%d%d%d",&opt,&x,&y);
		x^=ans;y^=ans;
		if(!opt){
			ans=query(x,0,y);//x子树内的 
			for(int i=x;pa[i];i=pa[i]){
				int dis=getdis(x,pa[i]);
				if(y>=dis)ans+=query(pa[i],0,y-dis)-query(i,1,y-dis);//父节点子树中x子树外的 
			}
			printf("%d\n",ans);
		}
		else modify(x,y-val[x]),val[x]=y;//单点修改 
	}//点分树的结构与原树的结构关系不大 所以距离什么的要放到第一次dfs里面处理出来 
	return 0;
}

标签:笔记,int,题解,pos,原树,点分,点分树,维护,dis
来源: https://www.cnblogs.com/h-lka/p/15087780.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有