首页 > 其他分享 >链剖分总结

链剖分总结

时间:2024-12-23 16:09:53浏览次数:4  
标签:总结 链剖分 重链 子树内 int lca LCA rt

来解决树上DS问题。

因为没有能够直接高效维护树型结构的DS,于是把树剖分成链,然后拿序列上的DS去维护每一条链的信息。

树链剖分有很多种:轻重链剖分,长链剖分,虚实链剖分。

轻重链剖分

这里是轻重链剖分。常数很小

其实不一定要用线段树维护,但用线段树维护是最常见的。

支持换根,路径修改,路径查询,子树修改,子树查询,查两点LCA。

钦定子树大小最大的儿子为当前节点\(u\)的重儿子\(son_u\),其他儿子为轻儿子,连向重儿子的边称为重边,连向轻儿子的边称为轻边,重边连起来成为重链。

一些性质:

  • 树上每个节点都属于且仅属于一条重链。

  • 重链链顶一定不是重儿子,显然的。

  • 剖分时重边优先遍历,那么一条重链中的\(dfn\)是连续的。\(dfn\)自身也有性质,一棵子树内的\(dfn\)是连续的。

  • 任意一个节点到根节点的路径上最多经过\(\log n\)条轻边和重链,那么树上任意一条路径都可以被拆成\(\log n\)条轻边和重链。

维护路径信息

由于链上的\(dfn\)连续,那么就是维护区间信息,再让\(u,v\)两点不断往上跳。

用线段树一次是\(O(\log^2 n)\)的。

维护子树信息

子树内\(dfn\)连续,那么就是维护区间信息。

用线段树一次是\(O(\log n)\)的。

求LCA

两点不断沿着重链往上跳,当跳到同一条重链上时,深度较浅的是LCA。

向上跳重链时先跳重链顶端深度较深的。

是\(O(\log n)\)的。

换根

换根后的路径与子树操作

luogu 遥远的国度

支持换根,路径赋值,查子树最小值。

\(Sol\):

注意到路径赋值其实和换根没什么关系,直接做就行。

换根肯定不能真换根,不然会炸。

考虑换根后新根和当前查询的点\(x\)的关系,大力分讨:

  • 新根是\(x\),那么输出整棵树的答案。

  • 新根在原树中不在\(x\)的子树内,那么\(x\)的子树答案不变。

  • 新根在原树中在\(x\)的子树内,设新根在原树中\(x\)的儿子\(y\)的子树内,那么答案就是整棵树扣掉\(y\)的子树的答案。

如何求\(y\)?若新根不断向上跳重链可以跳到和\(x\)在同一条重链上,且仍在\(x\)的子树内,那么\(y=son_x\)。同时\(y\)也可能是\(x\)的轻儿子,此时\(y\)是一条重链的链顶,所以跳的时候不断判断当前所在重链的链顶的父亲是否是\(x\)即可。

代码
#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int maxn=1e5+10;
const ll inf=0x7fffffff;
int n,m,tot=0,cnt=0,rt,head[maxn],id[maxn],dep[maxn],fa[maxn],son[maxn],tp[maxn],siz[maxn];
ll w[maxn],wt[maxn];
struct edge{
	int v,nxt;
}e[maxn<<1];

inline void add(int u,int v){
	e[++tot].v=v;
	e[tot].nxt=head[u];
	head[u]=tot;
}

struct TREE{
	ll mi,tag;
}t[maxn<<1];

#define ls(k) k<<1
#define rs(k) k<<1|1

inline ll min(ll x,ll y){
	return x<y?x:y;
}

inline void pushup(int k){
	t[k].mi=min(t[ls(k)].mi,t[rs(k)].mi);
}

inline void pushdown(int k){
	if(t[k].tag){
		t[ls(k)].tag=t[rs(k)].tag=t[k].tag;
		t[ls(k)].mi=t[rs(k)].mi=t[k].tag;
		t[k].tag=0;
	}
}

void build(int k,int l,int r){
	if(l==r){
		t[k].mi=wt[l];
		return;
	}
	int mid=(l+r)>>1;
	build(ls(k),l,mid);
	build(rs(k),mid+1,r);
	pushup(k);
	return;
}

void update(int k,int l,int r,int ql,int qr,int v){
	if(ql<=l&&r<=qr){
		t[k].tag=t[k].mi=v;
		return;
	}
	pushdown(k);
	int mid=(l+r)>>1;
	if(ql<=mid) update(ls(k),l,mid,ql,qr,v);
	if(mid<qr) update(rs(k),mid+1,r,ql,qr,v);
	pushup(k);
	return;
}

ll query(int k,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr) return t[k].mi;
	pushdown(k);
	int mid=(l+r)>>1;
	ll res=inf;
	if(ql<=mid) res=min(res,query(ls(k),l,mid,ql,qr));
	if(mid<qr) res=min(res,query(rs(k),mid+1,r,ql,qr));
	pushup(k);
	return res;
}

void dfs1(int u,int f){
	fa[u]=f;
	dep[u]=dep[f]+1;
	son[u]=-1;
	siz[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(son[u]==-1||siz[son[u]]<siz[v]) son[u]=v;
	}
}

void dfs2(int u,int p){
	tp[u]=p;
	id[u]=++cnt;
	wt[id[u]]=w[u];
	if(son[u]==-1) return;
	dfs2(son[u],p);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v); 
	}
}

void updline(int u,int v,int c){
	while(tp[u]!=tp[v]){
		if(dep[tp[u]]<dep[tp[v]]) swap(u,v);
		update(1,1,n,id[tp[u]],id[u],c);
		u=fa[tp[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	update(1,1,n,id[u],id[v],c);
}

int findson(int u,int v){
	while(tp[u]!=tp[v]){
		if(dep[tp[u]]<dep[tp[v]]) swap(u,v);
		if(fa[tp[u]]==v) return tp[u];
		u=fa[tp[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	return son[u];
}

ll qsubt(int u){
	if(u==rt) return query(1,1,n,1,n);
	if(id[u]>id[rt]||id[rt]>id[u]+siz[u]-1)  return query(1,1,n,id[u],id[u]+siz[u]-1);
	int sn=findson(rt,u);
	ll res=min(query(1,1,n,1,id[sn]-1),id[sn]+siz[sn]<=n?query(1,1,n,id[sn]+siz[sn],n):inf);
	return res;	
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;++i) scanf("%d",&w[i]);
	scanf("%d",&rt);
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	for(int i=1;i<=m;++i){
		int opt;
		scanf("%d",&opt);
		if(opt==1){
			int tmp;
			scanf("%d",&tmp);
			rt=tmp;
		}
		else if(opt==2){
			int x,y,v;
			scanf("%d%d%d",&x,&y,&v);
			updline(x,y,v);
		}
		else if(opt==3){
			int u;
			scanf("%d",&u);
			printf("%lld\n",qsubt(u));
		}
	}
	return 0;
}

换根后的LCA

CF Jimie and tree

主要就是求换根后的LCA。

以下记\(lca(u,v)\)表示原树上的LCA,\(LCA(u,v)\)表示换根后的LCA,\(rt\)表示新根。

换根后求\(x,y\)的LCA需要大力分讨(以下默认\(dep_x\le dep_y\)):

第一类:\(lca(x,y)=x\)时。即\(x,y\)在原树上时祖先后代关系。

  1. \(rt\)在\(x\)的子树内也在\(y\)的子树内,那么\(LCA(x,y)=y\)。

  2. \(rt\)在\(x\)的子树内却不在\(y\)的子树内,那么\(LCA(x,y)=lca(y,rt)\)。

  3. \(rt\)不在\(x\)的子树内,那么\(LCA(x,y)=x\)。

第二类:\(lca(x,y)\ne x\)。即\(x,y\)在原树上在不同的子树中。

  1. \(rt\)在\(x\)的子树内,那么\(LCA(x,y)=x\);\(rt\)在\(y\)的子树内,那么\(LCA(x,y)=y\)。

  2. \(rt\)在\(x\)到\(y\)的简单路径上,那么\(LCA(x,y)=rt\)。判断条件为\((lca(x,rt)=rt \&\&lca(y,rt)=lca(x,y))||(lca(y,rt)=rt\&\&lca(x,rt)=lca(x,y))\),另一种用\(dep\)判断的写法是\((lca(x,rt)=rt\&\&dep_{rt}\ge dep_{lca(x,y)})||(lca(y,rt)=rt\&\&dep_{rt}\ge dep_{lca(x,y)})\)。

  3. \(rt\)在\(lca(x,y)\)上方,那么\(LCA(x,y)=lca(x,y)\)。判断为\(lca(x,rt)=lca(y,rt)\)。

  4. \(rt\)在\(x\)到\(y\)的路径上的点\(u\)子树中,那么\(LCA(x,y)=u\)。\(lca(x,rt)=lca(x,y)\)时,\(u=lca(y,rt)\)。\(lca(y,rt)=lca(x,y)\)时,\(u=lca(x,rt)\)。

分讨,爽!!!

长链剖分

虚实链剖分

标签:总结,链剖分,重链,子树内,int,lca,LCA,rt
From: https://www.cnblogs.com/EmilyDavid/p/18624301

相关文章

  • 鸿蒙Next ArkTS编程规范总结
    一、目标和适用范围ArkTS编程规范参考业界标准及实践,结合ArkTS语言特点,旨在提高代码的规范、安全和性能,适用于开发者使用ArkTS编写代码的系统开发或应用开发场景。二、规则来源ArkTS在TypeScript基础上强化静态检查和分析,部分规则源于《OpenHarmony应用TS&JS编程指南》,并为ArkT......
  • 79.尚庭公寓项目总结收获
    单体项目技术来自尚硅谷:https://www.bilibili.com/video/BV1At421K7gP/?spm_id_from=333.337.search-card.all.click&vd_source=eb2341710c995d8261ecc99fdd066ba71.Typora的使用用的其实就跟博客园写记一样的markdown形式但我是习惯于win自带的记事本或是直接博客园但是......
  • 大语言模型学习工具及资源总结和落地应用
            当前,随着人工智能技术的迅猛发展,大语言模型(LargeLanguageModels,LLMs)在各个领域的应用日益广泛。以下是国内外常见的大语言模型工具、已经落地部署的应用以及学习相关的网站和资源的详细介绍。一、国内外常见的大语言模型工具国际大语言模型1.OpenAIGPT......
  • 【社工钓鱼】手法总结
    一、rlo文件名翻转简介:全名Right-to-LeftOverride,本质是一串Unicode字符,编码0x202E,本身不可见,插入之后会让在他之后的字符串从右往左重新排列,本意是用来支持一些从右往左写的语言的文字,比如阿拉伯语、希伯来语等,现可以用来伪造文件名后缀,结合上一些其他手段可用作钓鱼文件的制作......
  • 【安全运维】监控告警要点总结
    前言监控告警是业务稳定性建设非常重要的一环,告警项的配置、告警阈值的设置、告警信息的发送和响应,都影响着业务稳定性。随着系统版本迭代,监控告警工具的变更,人员的变动等诸多因素的变化,我们需要定期对监控告警的方方面面做复盘,不断优化提升监控告警,以最大程度保障业务稳定。202......
  • AI工具类总结(二),门槛低,简单易上手!
    SWE-agent:SWE-agent接受GitHub问题并尝试使用GPT-4或您选择的LM自动修复它。它还可以用于进攻性网络安全或竞争性编码挑战。Chat2DB:AI驱动的数据库工具和SQL客户端,最热门的GUI客户端,支持MySQL、Oracle、PostgreSQL、DB2、SQLServer、DB2、SQLite、H2、ClickHous......
  • 2-SAT总结
    基础部分有K-Satisfiability问题,但\(k\ge2\)时那是NPC的,\(k=1\)时是trivial的,所以讨论2-Satisfiability。问题是这样的:\(n\)个bool变量,\(m\)个限制条件,每个限制会给出对于两个bool变量之间关系的描述,如\(a_i\lora_j\)为真。求一组可行解。显然我们可以暴搜,这里不说了。我们......
  • Linux常用命令总结
    du-sh*:用于显示当前目录下每个文件和子目录的大小。以下是这个命令中各个部分的作用:du:代表"diskusage"(磁盘使用情况),用于估算文件和目录所占用的磁盘空间。-s:代表"summarize"(汇总),用于显示每个指定文件或目录的总大小,而不是每个文件的详细信息。-h:代表"human-readable"(......
  • mysql log两个参数总结
    摘录:https://developer.baidu.com/article/details/3279159在MySQL的InnoDB存储引擎中,有两个与日志相关的参数非常重要,分别是innodb_log_buffer_size和innodb_log_file_size。这两个参数对InnoDB的性能和可靠性都有很大的影响。下面我们将详细解释这两个参数的含义、如何调整它们......
  • 2024-2025-1 20241406刘书含 第十三周学习总结
    C语言与程序设计(一)文件文件指针:在C语言中,使用FILE类型定义文件指针,用来指向文件。用法为FILE*p。文件打开:使用fopen()函数打开文件文件关闭:使用fclose()函数关闭文件,其原型为intfclose(FILE*stream);。文件读写:fgetc()和getc()函数用于读取文件中的下一个字符。putc......