点分治学习笔记

点分治

点分治用于求解树上路径有关的问题。

其具体思想,对于当前处理的这一个分治区域,我们计算所有区域内跨过分治中心这一个点的所有路径的贡献,然后将分治中心及与其相邻的边删去,对于剩下的每一个分治区域再分别进行计算。

时间复杂度

看起来很暴力,实际上非常优秀,其关键就是我们对于分治中心的选取。一般而言,我们选择这一分治区域的重心

选择重心的原因是,删去重心后,剩下的没一个区域内的点数至少减半,这样的话,最多只会递归 logn 次,时间复杂度就是每一层处理的时间复杂度乘上 logn

具体做法

模板题 P4178 Tree

询问树上两点距离小于等于 k 的点对数量。

对于当前处理的分治区域,从分治中心开始进行 dfs ,处理出每个结点到达中心的距离,并排序,然后双指针扫过去就可以了。

  • 这样处理会有一个问题:有一些不合法的情况也会被计算在内。

如下图

因此我们考虑减去这样的不合法的情况,我们发现不合法是由于两个端点来自于同一个儿子,那么我们对于中心的每一个儿子,减去其所造成的不合法的贡献即可。

点击查看代码
inline void Getroot(int x,int f) {
	sz[x]=1;
	int now=0;
	for(int i=hd[x],y;i;i=ne[i]) 
		if(!vis[y=to[i]] && y!=f) 
			Getroot(y,x), sz[x]+=sz[y], now=max(now,sz[y]);
	now=max(now,all-sz[x]);
	if(now<Mx) Mx=now, Rt=x;
}

inline void Getdist(int x,int f,int dist) {
	sz[x]=1; tmp[++cnt]=dist;
	mx[Rt]=max(mx[Rt],dist);
	dis[x][cnt[x]]=dist; cnt[x]++;
	for(int i=hd[x],y;i;i=ne[i]) 
		if(!vis[y=to[i]] && y!=f) Getdist(y,x,dist+1), sz[x]+=sz[y];
}

inline void Solve(int x) {
	vis[x]=1;
	for(int i=hd[x],y;i;i=ne[i]) if(!vis[y=to[i]]) {
		Mx=inf; all=sz[y]; Getroot(y,0); 
		Getdist(Rt,0,0); New(Rt,mx[Rt],mx[x]);
		Fa[Rt]=x; Solve(Rt);
	}
}
inline int calc(int x,int dist0) {
    cnt=0; 
    Getdist(x,dist0);
    sort(tmp+1,tmp+1+cnt);
    int l=1,r=cnt,res=0;
    while(l<r) 
        if(tmp[l]+tmp[r]<=k) ret+=r-l, l++;
        else r--;
    return res;
}

点分树

点分树就是将每一个分治中心,向它所在区域内的下一级分治中心连边,由于每次选取的分治中心是重心,那么树高不会超过 logn ,这样我们每一次就可以暴力在树上进行查询和修改等操作。

具体的,对于每一个结点,维护两个信息:

T1[x] // 点分树上 x 子树内的点到 x 的距离
T2[x] // 点分树上 x 子树内的点到 Fa[x] 的距离
// Fa[x] 是 x 在点分树上的父亲结点
// 一般的,将距离做下标
  • 查询操作

对于 pRt 路径上的点 u,上一次查询的点为 las

查询 T1[x] 内区间 [0(kDis[p][x])] 的贡献并加上。

查询 T2[las] 内区间 [0(kDis[p][x])] 的贡献并减去,因为这样的路径是不合法的。

  • 修该操作

对于 pRt 路径上的点 u

T1[x] 中下标为 Dis[p][x] 的位置加上 va[p]

T2[x] 中下标为 Dis[p][Fa[x]] 的位置加上 va[p]

vector 用树状数组维护即可。

模板题P6329 【模板】点分树 | 震波

点击查看代码
inline void New(int x,int v1,int v2) { T1.tree[x].resize(v1+2); T2.tree[x].resize(v2+2); }

inline void Getroot(int x,int f) {
	sz[x]=1;
	int now=0;
	for(int i=hd[x],y;i;i=ne[i]) 
		if(!vis[y=to[i]] && y!=f) 
			Getroot(y,x), sz[x]+=sz[y], now=max(now,sz[y]);
	now=max(now,all-sz[x]);
	if(now<Mx) Mx=now, Rt=x;
}

inline void Getdist(int x,int f,int dist) {
	sz[x]=1;
	mx[Rt]=max(mx[Rt],dist);
	dis[x][cnt[x]]=dist; cnt[x]++;
	for(int i=hd[x],y;i;i=ne[i]) 
		if(!vis[y=to[i]] && y!=f) Getdist(y,x,dist+1), sz[x]+=sz[y];
}

inline void Solve(int x) {
	vis[x]=1;
	for(int i=hd[x],y;i;i=ne[i]) if(!vis[y=to[i]]) {
		Mx=inf; all=sz[y]; Getroot(y,0); 
		Getdist(Rt,0,0); New(Rt,mx[Rt],mx[x]);
		Fa[Rt]=x; Solve(Rt);
	}
}

inline void Build(int p) {
	int x=p,las=0,tmp=0;
	while(x) {
		T1.Modify(x,dis[p][tmp]+1,a[p]);
		if(Fa[x]) T2.Modify(x,dis[p][tmp+1]+1,a[p]);
		tmp++; x=Fa[x];
	}
}

inline void Change(int p,int v) {
	int x=p,tmp=0;
	while(x) {
		T1.Modify(x,dis[p][tmp]+1,v-a[p]);
		if(Fa[x]) T2.Modify(x,dis[p][tmp+1]+1,v-a[p]);
		tmp++; x=Fa[x];
	}
	a[p]=v;
}

inline int Ask(int p,int k) {
	int res=0,x=p,tmp=0,las=0;
	while(x) {
		if(k>=dis[p][tmp]) res+=T1.Query(x,k-dis[p][tmp]+1);
		if(las && k>=dis[p][tmp]) res-=T2.Query(las,k-dis[p][tmp]+1);
		tmp++; las=x; x=Fa[x];
	}
	return res;
}

待更。

posted @   _YangZJ  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩