首页 > 其他分享 >树链剖分学习笔记(补发)

树链剖分学习笔记(补发)

时间:2022-11-18 20:02:48浏览次数:69  
标签:f1 剖分 int tr 树链 num 补发 节点 mod

求大佬指错QaQ

个人推荐的题单:树链剖分练习题

个人感觉树链剖分就是把树上的节点按照某种顺序重新编号一次以便于用线段树、树状数组等维护。

这次讲讲轻重链剖分。模板题

一些概念

  • 重儿子:对于每一个非叶子节点,它的儿子中儿子数量最多的那一个儿子 为该节点的重儿子(图中2的重儿子为4,5的重儿子为8……)
  • 轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子(图中1的轻儿子为3,3的轻儿子为5)
  • 重边:一个节点连接该节点的重儿子的边叫做重边(图中的粗线)
  • 轻边:剩下的即为轻边(图中的细线)
  • 重链:相邻重边连起来的 连接一条重儿子 的链叫重链
  • 对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的重链
  • 每一条重链以根或轻儿子为起点

为了用线段树等维护,我们要保证每一个重链上的节点的新编号是连续的,所以新编号(括号内的)可以像下图一样。

为什么是重点维护重链,而不是别的链

为了提高速度,我们肯定要尽量让多个点在同一条重点维护的链上。

所以我们就把重点维护的链拉向重儿子,最终就是拉成了重链。

实际上也可以是与重儿子的儿子数量差不多的子结点为重链,而且这样更难卡

具体实现

首先要定义如下数组:

son[u]表示节点u的重儿子
siz[u]表示以节点u为子树有多少节点
dep[u]表示节点u的深度
fa[u]表示节点u的父节点
top[u]表示节点u所在重链的顶端节点的原编号
tid[u]表示节点u的新编号

之后就要将这些通过两个dfs求出来。

第一个:

void dfs1(int u) { //递归到的子树的根节点
	siz[u]=1;//先算上自己
	for(int i=head[u]; i; i=edge[i].next) { //枚举子节点
		int v=edge[i].v;
		if(v==fa[u])continue;//是父节点就跳过
		fa[v]=u;
		dep[v]=dep[u]+1;//深度+1
		dfs1(v);//递归
		if(siz[v]>siz[son[u]])
			son[u]=v;//以子树节点数最大的为重儿子
		siz[u]+=siz[v];//加上这个子树
	}
}

第二个:

void dfs2(int u,int tp) { //u为递归到的子树的根节点,tp表示u所在重链的起点
	tid[u]=++tot;
	top[u]=tp;
	if(son[u])
		dfs2(son[u],tp);//先拉出重链,保证重链编号连续
	for(int i=head[u]; i; i=edge[i].next) {
		int v=edge[i].v;
		if(v^fa[u]&&v^son[u])
			dfs2(v,v);//拉以轻儿子为起点的重链
	}
}

然后可以发现,不仅重链编号连续,而且同一子树的编号也连续(如图)。

所以以x为根的子树的最右端节点为tid[x]+siz[x]-1,然后我们的3,4操作就可以选择直接用线段树的update和query:

...
else if(op==3) {
	x=read(),y=read()%mod;
	update(1,tid[x],tid[x]+siz[x]-1,y);
} else if(op==4) {
	x=read();
	printf("%lld\n",query(1,tid[x],tid[x]+siz[x]-1));
}

对于1,2操作,我们执行以下操作直到两个点在同一条重链(如果在同一条重链可以直接算答案):

(设所在重链顶端的深度更深的那个点为x点)

  • ans加上x点到x所在重链端这一段区间的点权和
  • 把x跳到x所在链顶端的那个点的上面一个点

我们模拟一下(以下用w[x]表示节点x的权值)

在图中,我们若想求4到8的路径的权值和,执行就如下:

  1. 发现8的顶端5比4的顶端1更深。
  2. ans+=w[8]+w[5],8跳到顶端5的父节点2
  3. 发现此时2和4的顶端都为1(即在同一条重链上),于是ans+=w[2]+w[4],结束计算

更新也是一样的:

void upRange(int u,int v,ll val) {
	int f1=top[u],f2=top[v];
	while(f1^f2) {
		if(dep[f1]<dep[f2]) {
			swap(u,v);
			swap(f1,f2);
		}
		update(1,tid[f1],tid[u],val);
		u=fa[f1];
		f1=top[u];
	}
	if(dep[u]>dep[v])swap(u,v);
	update(1,tid[u],tid[v],val);
}
ll quRange(int u,int v) {
	ll ans=0;
	int f1=top[u],f2=top[v];
	while(f1^f2) {
		if(dep[f1]<dep[f2]) {
			swap(u,v);
			swap(f1,f2);
		}
		ans+=query(1,tid[f1],tid[u]);
		if(ans>=mod)ans-=mod;
		u=fa[f1];
		f1=top[u];
	}
	if(dep[u]>dep[v])swap(u,v);
	ans+=query(1,tid[u],tid[v]);
	return ans>=mod?ans-mod:ans;
}
...
if(op==1) {
	x=read(),y=read();
	upRange(x,y,read()%mod);
} else if(op==2) {
	x=read(),y=read();
	printf("%lld\n",quRange(x,y));
}

完整代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read() {
	bool f=1;
	ll x=0;
	int c=getchar();
	while(c<=47||c>=58) {
		f&=c!=45;
		c=getchar();
	}
	while(c>=48&&c<=57) {
		x=(x<<3)+(x<<1)+(c&15);
		c=getchar();
	}
	return f?x:-x;
}
const int MAXN=262144;
struct node {
	int l,r;
	ll sum,lt;
} tr[MAXN<<2];
int w[MAXN],wt[MAXN],n,m,root,mod;
int son[MAXN],siz[MAXN],top[MAXN],dep[MAXN],fa[MAXN],tid[MAXN];
int tot,ecnt,head[MAXN];
struct Edge {
	int u,v,next;
} edge[MAXN];
void addedge(int u,int v) {
	++ecnt;
	edge[ecnt].v=v;
	edge[ecnt].next=head[u];
	head[u]=ecnt;
}
void build(int num,int l,int r) {
	tr[num].l=l;
	tr[num].r=r;
	tr[num].lt=0;
	if(l==r) {
		tr[num].sum=wt[l];
		return ;
	}
	const int mid=l+r>>1,ls=num<<1,rs=ls|1;
	build(ls,l,mid),build(rs,mid+1,r);
	tr[num].sum=tr[ls].sum+tr[rs].sum;
	if(tr[num].sum>=mod)tr[num].sum-=mod;
}
void push_down(int num) {
	if(tr[num].lt) {
		const int ls=num<<1,rs=ls|1;
		tr[ls].lt+=tr[num].lt,tr[rs].lt+=tr[num].lt;
		if(tr[ls].lt>=mod)tr[ls].lt-=mod;
		if(tr[rs].lt>=mod)tr[rs].lt-=mod;
		tr[ls].sum=(tr[ls].sum+(tr[ls].r-tr[ls].l+1)*tr[num].lt)%mod;
		tr[rs].sum=(tr[rs].sum+(tr[rs].r-tr[rs].l+1)*tr[num].lt)%mod;
		tr[num].lt=0;
	}
}
void update(int num,int l,int r,ll val) {
	if(tr[num].l>=l&&r>=tr[num].r) {
		tr[num].sum=(tr[num].sum+val*(tr[num].r-tr[num].l+1))%mod;
		tr[num].lt+=val;
		if(tr[num].lt>=mod)tr[num].lt-=mod;
		return ;
	}
	push_down(num);
	const int ls=num<<1,rs=ls|1;
	if(l<=tr[ls].r)
		update(ls,l,r,val);
	if(tr[rs].l<=r)
		update(rs,l,r,val);
	tr[num].sum=tr[ls].sum+tr[rs].sum;
	if(tr[num].sum>=mod)tr[num].sum-=mod;
}
ll query(int num,int l,int r) {
	if(l<=tr[num].l&&tr[num].r<=r)
		return tr[num].sum;
	push_down(num);
	const int ls=num<<1,rs=ls|1;
	ll s=0;
	if(l<=tr[ls].r)
		s+=query(ls,l,r);
	if(tr[rs].l<=r)
		s+=query(rs,l,r);
	return s%mod;
}
void dfs1(int u) {
	siz[u]=1;
	for(int i=head[u]; i; i=edge[i].next) {
		int v=edge[i].v;
		if(v==fa[u])continue;
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		if(siz[v]>siz[son[u]])
			son[u]=v;
		siz[u]+=siz[v];
	}
}
void dfs2(int u,int tp) {
	tid[u]=++tot;
	wt[tot]=w[u];
	top[u]=tp;
	if(son[u])
		dfs2(son[u],tp);
	for(int i=head[u]; i; i=edge[i].next) {
		int v=edge[i].v;
		if(v^fa[u]&&v^son[u])
			dfs2(v,v);
	}
}
void upRange(int u,int v,ll val) {
	int f1=top[u],f2=top[v];
	while(f1^f2) {
		if(dep[f1]<dep[f2]) {
			swap(u,v);
			swap(f1,f2);
		}
		update(1,tid[f1],tid[u],val);
		u=fa[f1];
		f1=top[u];
	}
	if(dep[u]>dep[v])swap(u,v);
	update(1,tid[u],tid[v],val);
}
ll quRange(int u,int v) {
	ll ans=0;
	int f1=top[u],f2=top[v];
	while(f1^f2) {
		if(dep[f1]<dep[f2]) {
			swap(u,v);
			swap(f1,f2);
		}
		ans+=query(1,tid[f1],tid[u]);
		if(ans>=mod)ans-=mod;
		u=fa[f1];
		f1=top[u];
	}
	if(dep[u]>dep[v])swap(u,v);
	ans+=query(1,tid[u],tid[v]);
	return ans>=mod?ans-mod:ans;
}
int main() {
	scanf("%d%d%d%d",&n,&m,&root,&mod);
	for(int i=1; i<=n; ++i)
		w[i]=read()%mod;
	for(int i=1,u,v; i<n; ++i) {
		u=read(),v=read();
		addedge(u,v);
		addedge(v,u);
	}
	dfs1(root);
	dfs2(root,root);
	build(1,1,n);
	for(int op,i=1; i<=m; ++i) {
		ll x,y;
		op=read();
		if(op==1) {
			x=read(),y=read();
			upRange(x,y,read()%mod);
		} else if(op==2) {
			x=read(),y=read();
			printf("%lld\n",quRange(x,y));
		} else if(op==3) {
			x=read(),y=read()%mod;
			update(1,tid[x],tid[x]+siz[x]-1,y);
		} else if(op==4) {
			x=read();
			printf("%lld\n",query(1,tid[x],tid[x]+siz[x]-1));
		}
	}
	return 0;
}

例题1:P4315 月下“毛景树”

这题是边权,而不是点权,那要怎么办呢?

我们考虑把边权转为点权,而转给父节点的话,会因为一个节点有多个儿子而导致出错,我们就可以转给子节点。

代码上实现就和点权题差不多:
第一处,把点权赋给子节点:

void dfs1(int u) {
	siz[u]=1;
	for(int i=head[u]; i; i=edge[i].next) {
		int v=edge[i].v;
		if(v==fa[u])continue;
		dep[v]=dep[u]+1;
		fa[v]=u;
		w[v]=edge[i].w;//这里哦
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])
			son[u]=v;
	}
}

第二处:查询和更新的时候不能算上LCA(最近公共祖先),因为LCA的点权所代表的边权不在该路径上

int quRange(int u,int v) {
	int f1=top[u],f2=top[v],s=INT_MIN;
	while(f1^f2) {
		if(dep[f1]<dep[f2]) {
			swap(f1,f2);
			swap(u,v);
		}
		s=max(s,query(1,tid[f1],tid[u]));
		u=fa[f1],f1=top[u];
	}
	if(u==v)return s;//多了这一行
	if(dep[v]<dep[u])swap(u,v);
	return max(s,query(1,tid[son[u]]/*还有这里也改了*/,tid[v]));//把LCA去掉
}

第三处:单条边赋值时要选取子节点

if(c[1]=='h') {
	w=edge[x<<1].v,k=edge[(x<<1)-1].v;
	if(dep[w]<dep[k])w=k;
	update1(1,tid[w],tid[w],y);
}

完整代码:

#include <bits/stdc++.h>
using namespace std;
const int MAXN=131072;
inline int read() {
	int x=0;
	int c=getchar();
	bool f=1;
	while(c<=47||c>=58) {
		f&=c!=45;
		c=getchar();
	}
	while(c>=48&&c<=57) {
		x=(x<<3)+(x<<1)+(c&15);
		c=getchar();
	}
	return f?x:-x;
}
struct Edge {
	int v,w,next;
} edge[MAXN<<1];
int head[MAXN],ecnt;
void addedge(int u,int v,int w) {
	edge[++ecnt].v=v;
	edge[ecnt].w=w;
	edge[ecnt].next=head[u];
	head[u]=ecnt;
}
struct tree {
	bool fg;
	int mx,l,r,tg,lt;
} tr[MAXN<<2];
int n,d[MAXN][3],w[MAXN],top[MAXN],son[MAXN],fa[MAXN];
int tid[MAXN],tot,wt[MAXN],pid[MAXN],siz[MAXN],dep[MAXN];
void dfs1(int u) {
	siz[u]=1;
	for(int i=head[u]; i; i=edge[i].next) {
		int v=edge[i].v;
		if(v==fa[u])continue;
		dep[v]=dep[u]+1;
		fa[v]=u;
		w[v]=edge[i].w;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])
			son[u]=v;
	}
}
void dfs2(int u,int tp) {
	++tot;
	tid[u]=tot;
	pid[tot]=u;
	top[u]=tp;
	wt[tot]=w[u];
	if(!son[u])return;
	dfs2(son[u],tp);
	for(int i=head[u]; i; i=edge[i].next) {
		int v=edge[i].v;
		if(v^fa[u]&&v^son[u])
			dfs2(v,v);
	}
}
void build(int num,int l,int r) {
	tr[num].l=l;
	tr[num].r=r;
	tr[num].fg=tr[num].lt=tr[num].tg=0;
	if(l==r) {
		tr[num].mx=wt[l];
		return;
	}
	const int mid=l+r>>1,ls=num<<1,rs=ls|1;
	build(ls,l,mid),build(rs,mid+1,r);
	tr[num].mx=max(tr[ls].mx,tr[rs].mx);
}
void push_down(int num) {
	const int ls=num<<1,rs=ls|1;
	if(tr[num].fg) {
		tr[ls].mx=tr[ls].tg=tr[rs].mx=tr[rs].tg=tr[num].tg;
		tr[ls].fg=tr[rs].fg=1;
		tr[ls].lt=tr[rs].lt=0;
		tr[num].fg=0;
	}
	if(tr[num].lt) {
		tr[ls].mx+=tr[num].lt,tr[ls].lt+=tr[num].lt;
		tr[rs].mx+=tr[num].lt,tr[rs].lt+=tr[num].lt;
		tr[num].lt=0;
	}
}
void update1(int num,int l,int r,int val) {
	if(l<=tr[num].l&&tr[num].r<=r) {
		tr[num].fg=1;
		tr[num].mx=tr[num].tg=val;
		tr[num].lt=0;
		return;
	}
	push_down(num);
	const int ls=num<<1,rs=ls|1;
	if(l<=tr[ls].r)
		update1(ls,l,r,val);
	if(tr[rs].l<=r)
		update1(rs,l,r,val);
	tr[num].mx=max(tr[ls].mx,tr[rs].mx);
}
void update2(int num,int l,int r,int val) {
	if(l<=tr[num].l&&tr[num].r<=r) {
		tr[num].mx+=val;
		tr[num].lt+=val;
		return;
	}
	push_down(num);
	const int ls=num<<1,rs=ls|1;
	if(l<=tr[ls].r)
		update2(ls,l,r,val);
	if(tr[rs].l<=r)
		update2(rs,l,r,val);
	tr[num].mx=max(tr[ls].mx,tr[rs].mx);
}
int query(int num,int l,int r) {
	if(l<=tr[num].l&&tr[num].r<=r)
		return tr[num].mx;
	push_down(num);
	const int ls=num<<1,rs=ls|1;
	int s=INT_MIN;
	if(l<=tr[ls].r)
		s=max(s,query(ls,l,r));
	if(tr[rs].l<=r)
		s=max(s,query(rs,l,r));
	return s;
}
void upRange1(int u,int v,int val) {
	int f1=top[u],f2=top[v];
	while(f1^f2) {
		if(dep[f1]<dep[f2]) {
			swap(f1,f2);
			swap(u,v);
		}
		update1(1,tid[f1],tid[u],val);
		u=fa[f1];
		f1=top[u];
	}
	if(u==v)return ;
	if(dep[v]<dep[u])swap(u,v);
	update1(1,tid[son[u]],tid[v],val);
}
void upRange2(int u,int v,int val) {
	int f1=top[u],f2=top[v];
	while(f1^f2) {
		if(dep[f1]<dep[f2]) {
			swap(f1,f2);
			swap(u,v);
		}
		update2(1,tid[f1],tid[u],val);
		u=fa[f1];
		f1=top[u];
	}
	if(u==v)return ;
	if(dep[v]<dep[u])swap(u,v);
	update2(1,tid[son[u]],tid[v],val);
}
int quRange(int u,int v) {
	int f1=top[u],f2=top[v],s=INT_MIN;
	while(f1^f2) {
		if(dep[f1]<dep[f2]) {
			swap(f1,f2);
			swap(u,v);
		}
		s=max(s,query(1,tid[f1],tid[u]));
		u=fa[f1],f1=top[u];
	}
	if(u==v)return s;
	if(dep[v]<dep[u])swap(u,v);
	return max(s,query(1,tid[son[u]],tid[v]));
}
char c[10];
int main() {
	n=read();
	for(int i=1,u,w,v; i<n; ++i) {
		u=read(),v=read(),w=read();
		addedge(u,v,w);
		addedge(v,u,w);
	}
	dfs1(1);
	dfs2(1,1);
	build(1,1,n);
	int x,y,w,k;
	for(scanf("%s",c); c[0]!='S'; scanf("%s",c)) {
		x=read(),y=read();
		if(c[1]=='h') {
			w=edge[x<<1].v,k=edge[(x<<1)-1].v;
			if(dep[w]<dep[k])w=k;
			update1(1,tid[w],tid[w],y);
		} else if(c[1]=='o')
			upRange1(x,y,read());
		else if(c[1]=='d')
			upRange2(x,y,read());
		else if(c[1]=='a')
			printf("%d\n",quRange(x,y));
	}
	return 0;
}

标签:f1,剖分,int,tr,树链,num,补发,节点,mod
From: https://www.cnblogs.com/mod998244353/p/16904751.html

相关文章

  • 【LGR125D】【JRKSJ R5】Concvssion(多项式,长链剖分)
    Sub1:\(a_i=(i+1)\bmodn\)即图只有一个环。设\(g_u\)表示原来\(u\)上有多少个点,\(f_u=u\)表示\(u\)的点权。那么对于某个\(k\in[1,n]\),\(ans_k=\sum_{u}g_uf_......
  • 浅谈离线树链信息的一种并查集做法
    出处problem一棵树,点上有一些满足结合律的信息,\(m\)次询问求出一条链上的点权之“和”,允许离线。\(n,m\leq10^5\)。solution......
  • 树链剖分入门
    树链剖分入门本人初学,若有错误恳请大佬在评论区指出,谢谢!一,它能干嘛恶心你解决树上路径/子树等问题。情景引入老师:给一棵点权树和一些操作,每次操作选两个点,把这两个点......
  • Luogu4315 月下“毛景树” - 树链剖分 -
    题目链接:https://www.luogu.com.cn/problem/P4315题意:一棵有边权的树,维护树上的链加、链覆盖、修改边权、链上max题解:好难写...首先把边权转化为儿子的点权然后树链剖......
  • CF487E Tourists(Tarjan,圆方树,树链剖分,线段树)
    CF487ETourists带权无向图\(N,M\),\(Q\)次询问\(s,t\)所有不经过重复点可能路径经过的最小值的最小值。CODE每次修改一个圆点\(u\)周围的方点有点难。可是李神......
  • 【十二省联考2019】希望(容斥,换根DP,长链剖分,懒标记)
    暴力的想法是考虑钦定每个点作为到达点并统计贡献,但显然这样会算重。注意到会算重的到达点一定构成了一个连通块,这是一个很好的性质,方便了我们容斥:我们直接用点的贡献减去......
  • 【XSY3726】大猫熊和他的k边形(三角剖分,卡特兰数)
    记录一下两个结论。有标号\(n\)边形的三角剖分数等于\(B_{n-2}\),其中\(B\)是卡特兰数。证明:考虑DP,设\(C_n\)为有标号\(n\)边形的三角剖分数,考虑与\(1\)号......
  • 【XSY3679】农民(树链剖分)
    题面题解先考虑一个节点怎么样才会被走到。对于一个权值为为\(x\)的节点,它的左子树内的节点有可能被走到仅当其权值小于\(x\),右子树内的节点有可能被走到仅当其......
  • 【XSY3633】匹配(树形 DP,树链剖分,分治)
    考虑最普通的DP:设\(f_{u,i,0/1}\)表示\(u\)子树内恰好包含\(i\)条边的最大权匹配,其中\(u\)有无被匹配。这是个树上背包,暴力DP是\(O(n^2)\)的。可以发现\(f......
  • 【WC2010】重建计划(分数规划+长链剖分)
    长链剖分因为有很多巨佬只是讲了一下大致的做法,并没有详细地解释如何维护,所以就有了这篇题解。巨佬们都不屑于详细写,我太弱了/kk首先先对原树进行长链剖分。先讲一些定......