点分治与点分树 学习笔记

news/2024/7/20 20:10:24 标签: 学习, 深度优先, 算法

开始营业qwq

点分治

点分治就是把树上的点分治一下,可以批量处理树上和路径相关的问题。

每次根据重心划分,把树分成 O ( log ⁡ ) O(\log) O(log) 个连通块处理答案,然后合并。

Tree

传送门

模板题,把连通块内每一个节点到当前根的距离求出并排序,双指针扫描即可。

这样做会多算一些 LCA 不是当前根的点对,有两个方法解决:分别对每个子节点算出答案进行容斥;或者将每个子节点的子树染成不同颜色,双指针扫描过程中动态维护 [ l . r ] [l.r] [l.r] 区间的每个颜色的数量,颜色与 l l l 相同的不算贡献。

C o d e Code Code:(容斥)

#include<bits/stdc++.h>
#define ll long long
#define ff(i,s,e) for(int i=(s);i<=(e);++i)
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=4e4+5;
int n,k;
int head[N],cnt;
struct qwq{
	int v,w,nxt;
}e[N<<1];
inline void add(int u,int v,int w){
	e[++cnt]=qwq{v,w,head[u]},head[u]=cnt;
}
int ans,d[N],tot;
int rt,mx[N],siz[N];
bool vis[N];
void find(int x,int fa,int tot){
	siz[x]=1,mx[x]=0;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa||vis[v]) continue;
		find(v,x,tot),siz[x]+=siz[v];
		mx[x]=max(mx[x],siz[v]);
	}
	mx[x]=max(mx[x],tot-mx[x]);
	if(rt==-1||mx[x]<mx[rt]) rt=x;
}
void get(int x,int fa,int dis){
	d[++tot]=dis;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;
		if(vis[v]||v==fa) continue;
		get(v,x,dis+w);
	}
}
inline int calc(int x,int dis){
	tot=0,get(x,0,dis);
	sort(d+1,d+tot+1);
	int l=1,r=tot,ans=0;
	while(l<=r){
		if(d[l]+d[r]<=k) ans+=r-l,++l;
		else --r;
	}
	return ans;
}
void solve(int x){
	vis[x]=1,ans+=calc(x,0);
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;
		if(vis[v]) continue;
		ans-=calc(v,w);
	}
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(vis[v]) continue;
		rt=-1,find(v,0,siz[v]);
		solve(rt);
	}
}
signed main(){
	n=read();
	ff(i,2,n){
		int u=read(),v=read(),w=read();
		add(u,v,w),add(v,u,w);
	}
	rt=-1,find(1,0,n);
	k=read();
	solve(rt);
	printf("%d\n",ans);
}

点分树

是个神奇的东西,在带修的求距离等与树形态关系不大的问题时,可以将树重构,使得树高为 O ( log ⁡ ) O(\log) O(log),以支持一些暴力算法

具体来说就是连接每个点与其子树的重心。重构之后的树上距离和父子关系等都与原树无关,需要在原树上求解。所以一种一般的解法就是对每个点使用两个数据结构维护自己的信息、自己的子树对点分树上的父节点的贡献。

点分树和原树少得可怜的联系:

  • 点分树上两点LCA一定在原树这两点间的路径上
  • 点分树的一个子树在原树上一定联通

点分树 | 震波

传送门

模板题,对每个点 x x x 维护 x x x 子树内到 x x x 的距离相同的点的权值和,以及 x x x 子树内对 x x x 在点分树上父亲 f a fa fa 的距离相同的点的权值和,用两个树状数组 t 1 t1 t1 t 2 t2 t2 即可解决。

由于树高为 log ⁡ n \log n logn,每个节点最多被统计 log ⁡ n \log n logn 次,对每个树状数组按需要的空间用 vector 开点,空间复杂度即为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

对于每次查询,在点分树上暴力向上跳,每次加上父亲的贡献,减去父亲和当前点被重复计算的贡献即可;修改也是暴力上跳修改树状数组,时间复杂度均为 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)

代码里标出了部分易错细节:

#include<bits/stdc++.h>
#define ll long long
#define ff(i,s,e) for(int i=(s);i<=(e);++i)
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+5;
int n,m,a[N];
int head[N],cnt;
struct qwq{
	int v,nxt;
}e[N<<1];
inline void add(int u,int v){
	e[++cnt]=qwq{v,head[u]},head[u]=cnt;
}
struct BIT{
	vector<int> t;
	int n;
	inline void build(int x){
		n=x+2;
		ff(i,1,n+1) t.push_back(0);
		//n+1保证不会非法访问 
	}
	inline int lowbit(int x){return x&(-x);}
	inline void upd(int x,int y){
		for(int i=x;i<=n;i+=lowbit(i)) t[i]+=y;
	}
	inline int query(int x){
		int res=0;
		for(int i=min(x,n);i;i-=lowbit(i)) res+=t[i];
		return res;
	}
}t1[N],t2[N];
namespace LCA{
	int dep[N],f[N][20];
	inline int lca(int x,int y){
		if(dep[x]<dep[y]) swap(x,y);
		for(int i=18;i>=0;--i) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
		if(x==y) return x;
		for(int i=18;i>=0;--i) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
		return f[x][0];
	} 
	inline int dis(int x,int y){
		return dep[x]+dep[y]-(dep[lca(x,y)]<<1);
	}
	void dfs(int x,int fa){
		dep[x]=dep[fa]+1,f[x][0]=fa;
		ff(i,1,18) f[x][i]=f[f[x][i-1]][i-1];
		for(int i=head[x];i;i=e[i].nxt){
			int v=e[i].v;
			if(v!=fa) dfs(v,x);
		}
	}
}
int siz[N],mx[N],rt,f[N];
vector<int> g;
bool vis[N];
void find(int x,int fa,int tot){
	siz[x]=1,mx[x]=0;
	g.push_back(x);
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa||vis[v]) continue;
		find(v,x,tot),siz[x]+=siz[v];;
		mx[x]=max(mx[x],siz[v]);
	}
	mx[x]=max(mx[x],tot-siz[x]);
	if(rt==-1||mx[x]<mx[rt]) rt=x;
}
void dfs(int x,int fa,int rt,int dis){
	t1[rt].upd(dis,a[x]);
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa||vis[v]) continue;
		dfs(v,x,rt,dis+1);
	}
}
void init(int x,int fa){
	f[x]=fa,vis[x]=1;
	t1[x].build(g.size()),t2[x].build(g.size());
	if(fa) for(int now:g) t2[x].upd(LCA::dis(fa,now),a[now]);
	//不要忘了if(fa)的特判 
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(!vis[v]) dfs(v,x,x,1);
	}
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(vis[v]) continue;
		rt=-1,g.clear(),find(v,x,siz[v]);
		init(rt,x);
	}
}
inline int query(int x,int k){
	int ans=a[x]+t1[x].query(k),xx=x;
	while(f[x]){
		int d=LCA::dis(f[x],xx);
		if(k>=d) ans+=a[f[x]]+t1[f[x]].query(k-d)-t2[x].query(k-d);
		x=f[x];
	}
	return ans;
}
inline void upd(int x,int y){
	int xx=x;
	while(f[x]){
		int d=LCA::dis(xx,f[x]);
		t2[x].upd(d,y-a[xx]);
		x=f[x];
		t1[x].upd(d,y-a[xx]);
	}
	a[xx]=y;
}
signed main(){
	n=read(),m=read();
	ff(i,1,n) a[i]=read();
	ff(i,2,n){
		int u=read(),v=read();
		add(u,v),add(v,u); 
	}
	LCA::dfs(1,0);
	rt=-1,find(1,0,n);
	init(rt,0);
	int lst=0;
	while(m--){
		int op=read(),x=read()^lst,y=read()^lst;
		if(!op) printf("%d\n",lst=query(x,y));
		else upd(x,y);
	}
}

[ZJOI2015]幻想乡战略游戏

传送门

自己做的时候甚至不知道怎么在点分树上利用树高性质统计答案/kk

考虑u和它的子节点 v v v ( u , v ) = w (u,v)=w (u,v)=w,若补给站从 u u u 移到 v v v v v v 及其子树少移动了 s u m d [ v ] × w sumd[v]\times w sumd[v]×w u u u 及其它子树上的点多移动了 ( s u m d [ u ] − s u m d [ v ] ) × w (sumd[u]-sumd[v])\times w (sumd[u]sumd[v])×w,那么当且仅当 2 s u m d [ v ] > s u m d [ u ] 2sumd[v]>sumd[u] 2sumd[v]>sumd[u] 时移到 v v v 更优,于是同一个点的子节点至多只能选一个,复杂度就是 O ( log ⁡ n ) O(\log n) O(logn) 了。

于是套路性地维护 d 1 [ x ] = ∑ v ∈ x 子树 d i s ( v , x ) × d [ v ] d1[x]=\sum\limits_{v\in x子树}dis(v,x)\times d[v] d1[x]=vx子树dis(v,x)×d[v] d 2 [ x ] = ∑ v ∈ x 子树 d i s ( v , f a [ x ] ) × d [ v ] d2[x]=\sum\limits_{v\in x子树} dis(v,fa[x])\times d[v] d2[x]=vx子树dis(v,fa[x])×d[v] f a [ x ] fa[x] fa[x] 为点分树上 x x x 的父节点),以及 s u m d [ x ] = ∑ v ∈ x 子树 d [ v ] sumd[x]=\sum\limits_{v\in x子树} d[v] sumd[x]=vx子树d[v] 即可。由于每次要从原树的父节点转移到子节点,所以对每个点 x x x 应记录它在点分树上的儿子以及这个儿子是由原树上 x x x 的哪个儿子求重心得到的,根据原树儿子的答案来判断是否转移到点分树的儿子上。

然后懒得重写倍增 LCA 把上一题代码复制过来, d e p dep dep 和点到跟距离应该是两个数组否则 LCA 会错啊,调了一上午/fn/fn/fn


http://www.niftyadmin.cn/n/22689.html

相关文章

ARM64内存虚拟化分析(8)coalesed MMIO处理

从前面MMIO的处理可以看到&#xff0c;每次访问MMIO都会导致虚拟机退了到QEMU中。很多时候多个MMIO操作&#xff0c;这个时候可以先将前面的MMIO操作保存起来&#xff0c;等到最后一个MMIO的时候&#xff0c;再一起退出到QEMU中处理&#xff0c;这就是coalesced MMIO。目前只支…

WebDAV之葫芦儿·派盘+File Manager

File Manager 支持WebDAV方式连接葫芦儿派盘。 手机文件太多,空间不足、隐藏文件多、文件清理不干净?推荐您一个功能强大的文件管理器,可以让你对手机中的各类文件进行管理,支持快速移动、复制粘贴、压缩解压等等。同时还能对已经安装的程序进行卸载,自动识别手机中的AP…

好用的开源个人博客推荐

原文网址&#xff1a;好用的开源个人博客推荐_IT利刃出鞘的博客-CSDN博客 简介 本文推荐个人从几十款开源个人博客中精选的几款开源博客。 halo Github 地址 &#xff1a;https://github.com/halo-dev/halo Star : 24.3k 简介 &#xff1a;一个优秀的开源博客应用。 技术 …

上海亚商投顾:沪指缩量跌0.44% 医药股全线反弹

上海亚商投顾前言&#xff1a;无惧大盘大跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪大小指数今日走势分化&#xff0c;沪指全天弱势震荡&#xff0c;创业板指盘中涨超1%&#xff0c;午后涨幅有所回落。…

C# 异步编程

一 异步编程 1 异步 asynchronize 2 主要解决的事情是 ① 等待一些耗时的任务&#xff08;特别是文件&#xff0c;网络操作&#xff09;而不阻塞当前任务&#xff1b; ② 异步编程提高响应能力&#xff08;特别是UI&#xff09; 开始一个任务后&#xff0c;让任务在离感应线…

用QT实现一个模型交互的网络请求

最近&#xff0c;我接收到了一个项目需求&#xff0c;具体内容如下&#xff1a; 具体要求&#xff1a; 1.交付给我程序的源代码即可&#xff0c;因为我要集成到我的大软件中&#xff0c;要求采用C和QT开发&#xff1b; 2.程序首先检测当前用户环境有没有联网&#xff0c;如果没…

进程优先级环境变量进程地址空间

目录 一、进程优先级 1、概念 2、查看 3、其他概念 二、环境变量 1、基本概念 2、常见环境变量 3、查看环境变量的方法 4、和环境变量相关的命令 5、环境变量的组织方式 6、通过系统调用获取或设置环境变量 三、程序地址空间 一、进程优先级 1、概念 cpu资源分配的…

Allegro如何实现交换pin操作详细指导

Allegro如何实现交换pin操作详细指导 在做PCB设计的时候,换pin是用的较多的功能,换pin可以让线序更加的顺,方便布线。但是前提是确保网络的交换是被允许的 下面用下图为例介绍Allegro中是如何实现交换pin的 具体操作如下 选择File选择Export-Libraries