首页 > 编程语言 >【学习笔记】部分树上算法(概念篇)

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

时间:2024-01-28 15:55:05浏览次数:24  
标签:重链 int tp tr dfs dep 算法 笔记 树上

本文包括:

轻重链剖分(done)

线段树合并(done)

to be upd:

长链剖分

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

点分治

边分治

LCT

有待更新

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

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

part1 轻重链剖分

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

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

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

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

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

dfs序

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

设置一个时间,从1开始

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

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

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

于是我们可以先设一个数组 \(dfn_x\) 表示编号为 \(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可以说互相平替,这玩意空间大一些但是好搞

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

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

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]<<" ";
}

标签:重链,int,tp,tr,dfs,dep,算法,笔记,树上
From: https://www.cnblogs.com/exut/p/17991322

相关文章

  • 单源最短路径算法之bellman-ford
    单源最短路径算法之\(bellman-ford\)以边为研究对象单起点多终允许有负边权\(bellman-ford\)的工作原理假设\(n\)个点\(m\)条有向边的图,求\(s\)为起点的最短路条以\(s\)出发的最短路,最多包含\(n\)个点,\(n-1\)条边对于一条边\((x,y,w)\),\(y\)可能被\(x\)......
  • 《人月神话》读书笔记2
    贯彻执行要保证项目开发概念的完整性,可以通过手册规范开发人员的工作;尽管形式化定义通常难以理解,但它可以让表达更加精确;会议可以实现开发人员、架构师等团队内部的沟通协商,会议留下的问题积攒后通过大会重新决策。为什么巴比伦塔会失败?团队内部的沟通交流将影响整个团队的开发......
  • 线段树笔记
    voidpushup(inttr){ seg[tr]=seg[tr*2]+seg[tr*2+1];}voidbuild(inttr,intl,intr){ if(l==r){ seg[tr]=a[r]; return; } intmid=(l+r)/2; build(tr/2,l,mid); build(tr/2,mid+1,r); pushup(tr);}voidpushdown(inttr,intl,intr){ if(pls[tr]==0)......
  • 《构建之法》读书笔记2
        软件件开发分为几个阶段:玩具阶段→业余爱好阶段→探索阶段→成熟的产业阶段。而在我们学习软件开发时也会经历以下几个阶段,首先是玩具阶段,这个阶段可能也就像我们高考完填报志愿时那样,对计算机有点兴趣,幻想着做出什么有意思的软件。这个时候我们对软件这个东西还是不......
  • 《构建之法》阅读笔记3
        最后,邹欣探讨了团队协作和伦理责任在软件构建中的重要性。作者认为,一个成功的软件项目不仅需要技术上的卓越,更需要团队之间的良好合作和沟通。    首先,作者分析了团队协作的关键因素,包括沟通、信任、以及分工合作等方面。他提出了一些有效的团队管理策略和方......
  • 《构建之法》读书笔记1
         《构建之法》一书由软件工程领域的专家邹欣撰写,旨在探索现代软件工程的核心理念和关键实践。软件构建作为软件开发生命周期中的关键环节,对于确保软件质量、可维护性和可扩展性至关重要。在本书的第一篇中,邹欣深入剖析了构建的本质,并提出了一系列构建策略和方法。 ......
  • 算法模板 v1.4.1.20240128
    算法模板v1.1.1.20240115:之前的历史版本已经不可寻,创建了第一份算法模板。v1.2.1.20240116:删除“编译”-“手动开栈”与“编译”-“手动开O优化”;将“编译”-“CF模板”中的第20行代码cin>>T;注释;删除“读写”及其目录下的内容;删除“图论”-“欧拉图”-“混合图”;删除“图论”-......
  • 《构建之法》阅读笔记1
      《构建之法:现代软件工程》是邹欣所著的一部引人深思的著作,它引领读者深入了解软件工程的本质,并提出了许多新颖而富有洞见的观点。   在书中,邹欣首先强调了软件工程中模块化与组件化的重要性。他指出,通过将复杂的系统分解为更小的模块,我们能够更轻松地管理和维护代码......
  • 《设计模式之禅》读书笔记
    参考  https://zhuanlan.zhihu.com/p/357889775 一、六大设计原则单一职责原则定义:应该有且仅有一个原因引起类的变更。举例:属性和行为拆分,例如setPassword(Stringpassword)和changePassword(Stringpassword)。单一职责原则提出了一个编写程序的标准,用“职责”或“......
  • CSAPP学习笔记——Chapter12 并行编程
    CSAPP学习笔记——Chapter12并行编程并发编程有着其独特的魅力,之前接触cuda编程的时候,感受到一些,没想到书里还有相关的内容。今天我们主要围绕进程,I/O多路复用,线程三种并发的方式,介绍并发编程的相关概念。并最终拓展chapter11讲中的echo服务器,使其能够处理多个客户端的连接请求......