【学习笔记】部分树上算法(概念篇)

本文包括:

轻重链剖分(done)

线段树合并(done)

to be upd:

长链剖分

DSU on tree(树上启发式合并)

点分治

边分治

LCT

有待更新

本文非例题代码大多未经过编译,谨慎使用

本文本来只有重剖长剖dsu,但是发现不会写,另外几个甚至更简单就带歪了.jpg

part1 轻重链剖分

树剖是一类算法的总称,主要分为三个家伙:

轻重链剖分、长链剖分、实链剖分

实链剖分一般只在LCT会用到,略去(我不会)

假如我们现在有一棵树,我们想要对他完成链上和子树上的一些操作(如子树加、子树和查询、链加链查询)

首先我们需要了解一些子树问题怎么做

dfs序

我们从根出发,向下进行dfs

设置一个时间,从1开始

第一次经过一个点时,把这个点的dfs序设置为当前时间,并且把时间+1

可能会有点疑惑这个玩意干啥用的

最大的用处是,同一个子树内的点的dfs序是连续的

于是我们可以先设一个数组 dfnx 表示编号为 x 的点的 dfs序,此时再维护一个数组表示每个dfs序对应的点的点权,此时我们只需要维护一个线段树之类的数据结构就可以维护子树操作了

求dfs序的代码:

int tim;//时间戳
int a[N];//原点权(下标为编号)
int w[N];//新点权(下标为dfs序) 
void dfs(int x,int fa){//当前节点及父亲 
    dfn[x]=++tim;
    w[dfn[x]]=a[x];
	for(i=0;i<e[x].size();i++){
		if(e[x][i]==fa) continue;
		dfs(e[x][i],x);
	}
}

剖分

此时加入了链操作,事情就略微复杂了

我们朴素的dfs序无法解决链上问题(so bad)

我们需要一点点高档货——重链剖分

我们定义:对于一个节点。他的所有儿子中子树大小最大的儿子称为重儿子(有大小相等的随便选一个就行),其余为轻儿子

我们又可以定义:由一个轻儿子开始,后续全部由重儿子组成的链称为一条重链(根是轻儿子)

我们有一个结论:从一个节点到根的路径上,重链数量不超过 logn 条,证明略去
这个性质很好的保证了我们的复杂度

我们不妨先用一个dfs去找重儿子并且处理深度之类的信息

void dfs1(int x,int ft){
	siz[x]=1;
	dep[x]=dep[ft]+1; 
	fa[x]=ft;
	for(int i=0;i<e[x].size();i++){
		if(e[x][i]==ft) continue;
		dfs1(e[x][i],x);
		siz[x]+=siz[e[x][i]];
		if(!son[x] or siz[e[x][i]]>siz[son[x]]) son[x]=e[x][i]; 
	}	
}

然后我们在标dfs序的时候先标重儿子的,这样重链上的dfs序将是连续的。顺便记录一下每条重链的顶端

void dfs2(int x,int top){
	dfn[x]=++tim;
	w[tim]=a[x];
	tp[x]=top;
	if(son[x]) dfs2(son[x],top);
	for(int i=0;i<e[x].size();i++){
		if(e[x][i]==son[x] or e[x][i]==fa[x]) continue;
		dfs2(e[x][i],e[x][i]); 
	}
}

考虑怎么处理链上操作询问:直接往上跳

找出 x y(路径两端)当前重链顶端更深的那个跳到重链顶端的父亲

跳一次就把跳过去的部分修改、查询搞定

直到两个在同一条重链,就把两点间修改了

void mli(int x,int y,int z){//修改
	while(tp[x]!=tp[y]){
		if(dep[tp[x]]<dep[tp[y]]) swap(x,y);
		add(1,1,n,dfn[tp[x]],dfn[x],z);
		x=fa[tp[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	add(1,1,n,dfn[x],dfn[y],z);
}
//查询
int qli(int x,int y){
	int ans=0;
	while(tp[x]!=tp[y]){
		if(dep[tp[x]]<dep[tp[y]]) swap(x,y);
		ans+=query(1,1,n,dfn[tp[x]],dfn[x]);
		x=fa[tp[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return ans+query(1,1,n,dfn[x],dfn[y]);
}

借助跳,我们也可以求 LCA

int LCA(int x,int y){
	while(tp[x]!=tp[y]){
		if(dep[tp[x]]<dep[tp[y]]) swap(x,y);
		x=fa[tp[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return x;
}

线段树合并

看起来这个玩意不是很像那种处理树上问题的东西,确实,但是这个玩意最大的应用就是树上问题

这玩意和DSU on tree可以说互相平替,这玩意空间大一些但是好搞,但是dsu大多数时候多一只log

合并的是动态开点权值线段树

首先直接放出合并代码因为真的没啥好说的,暴力递归一个一个搞

int merge(int a,int b,int l,int r){
	if(!a or !b) return a+b;
	if(l==r){
		tr[a].max+=tr[b].max;
		tr[a].ans=l;
		return a;
	}
	int mid=(l+r)>>1;
	tr[a].l=merge(tr[a].l,tr[b].l,l,mid);
	tr[a].r=merge(tr[a].r,tr[b].r,mid+1,r);
	pup(a);
	return a;
}

看题:Lomsat gelral

dsu可做,但是不要

我们给每个节点搞一个动态开点权值线段树

然后给每个点记录一下区间最大值和每个结点的答案,pushup更新

然后dfs从叶子向上dfs回溯算完子节点再合并给父结点

#include<bits/stdc++.h>
#define int long long
#define ls tr[now].l
#define rs tr[now].r
using namespace std;

struct SEG{
	int l,r,max,ans;
}tr[5000050];

int rt[100010],cl[100010];
int cnt,n,ass[100010];
vector<int> e[100010];

void pup(int now){
	if(tr[ls].max>tr[rs].max){
		tr[now].max=tr[ls].max;
		tr[now].ans=tr[ls].ans;
	}
	else if(tr[ls].max<tr[rs].max){
		tr[now].max=tr[rs].max;
		tr[now].ans=tr[rs].ans;
	}
	else if(tr[ls].max==tr[rs].max){
		tr[now].max=tr[ls].max;
		tr[now].ans=tr[ls].ans+tr[rs].ans;
	}
} 

void upd(int &now,int l,int r,int pos){
	if(!now) now=++cnt;
	if(l==r){
		tr[now].max++;
		tr[now].ans=l;
		return;
	} 
	int mid=(l+r)>>1;
	if(pos<=mid) upd(ls,l,mid,pos);
	else upd(rs,mid+1,r,pos);
	pup(now);
} 

int merge(int a,int b,int l,int r){
	if(!a or !b) return a+b;
	if(l==r){
		tr[a].max+=tr[b].max;
		tr[a].ans=l;
		return a;
	}
	int mid=(l+r)>>1;
	tr[a].l=merge(tr[a].l,tr[b].l,l,mid);
	tr[a].r=merge(tr[a].r,tr[b].r,mid+1,r);
	pup(a);
	return a;
}

void dfs(int now,int fa){
	for(int i=0;i<e[now].size();i++){
		int to=e[now][i];
		if(to==fa) continue;
		dfs(to,now);
		merge(rt[now],rt[to],1,100000);
	}
	upd(rt[now],1,100000,cl[now]);
	ass[now]=tr[rt[now]].ans;
}

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>cl[i];
		rt[i]=i;
		cnt++;
	}
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++) cout<<ass[i]<<" ";
}
posted @   exut  阅读(30)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
Title
点击右上角即可分享
微信分享提示