树链剖分tips-边权转点权

众所周知,树剖是个好东西

我们可以通过树链剖分,将一个树分成多个“序号连续”的链,因为新赋予节点的新序号连续,所以可以用许多数据结构,例如线段树与树状数组来维护区间信息

有了树剖+线段树,我们可以维护两个点之间路径上点的相关信息,以及任意节点子树(包括自己)的信息。前者序号连续是因为树链剖分优先重儿子的特性,后者序号连续则是因为dfs的特性

树剖模板

#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 100005
#define MAXM 100003
#define eps 1e-9
#define foru(a,b,c)	for(int a=b;a<=c;a++)
#define ford(a,b,c)	for(int a=b;a>=c;a--)
#define RT return 0;
#define db(x)	cout<<endl<<x<<endl;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
	LXF x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9'){ 
	if(ch=='-') w=-1;
	ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
	x=x*10+(ch-'0');
	ch=getchar();
	}
	return x*w;
}
inline void out(LXF x){
	if(x<0){
	x=-x;
	putchar('-');
	}
	if(x>9) out(x/10);
	putchar(x%10+'0');
}
int n,m,r,mod,son[MAXN],a[MAXN],dep[MAXN],at[MAXN],cnt,id[MAXN],top[MAXN],siz[MAXN],fa[MAXN];
vector<int> e[MAXN];
struct Segtree{
	int l,r,lz,sum;
	void clear(){lz=0;}
}tr[MAXN<<2];
//线段树部分
inline int lc(int p){return p<<1;}
inline int rc(int p){return 1+(p<<1);}
void update(int p,int k){
	tr[p].sum+=((tr[p].r-tr[p].l+1)*(k%mod))%mod;
	tr[p].sum%=mod;
	tr[p].lz+=k;
	// tr[p].lz%=mod;
}
void push_down(int p){
	update(lc(p),tr[p].lz);
	update(rc(p),tr[p].lz);
	tr[p].clear();
}
void push_up(int p){
	tr[p].sum=(tr[lc(p)].sum+tr[rc(p)].sum)%mod;
}
void build(int p,int l,int r){
	tr[p].l=l,tr[p].r=r;
	if(l==r){
		tr[p].sum=at[l]%mod;
		return ;
	}
	int mid=(l+r)>>1;
	build(lc(p),l,mid);
	build(rc(p),mid+1,r);
	push_up(p);
}
void modify(int p,int nl,int nr,int k){
	int l=tr[p].l,r=tr[p].r;
	if(nr<l||r<nl)	return ;
	if(nl<=l&&r<=nr){
		update(p,k);
		return ;
	}
	push_down(p);
	modify(lc(p),nl,nr,k);
	modify(rc(p),nl,nr,k);
	push_up(p);
}
int qurey(int p,int nl,int nr){
	int l=tr[p].l,r=tr[p].r;
	if(nr<l||r<nl)	return 0;
	if(nl<=l&&r<=nr)	return tr[p].sum%mod;
	push_down(p);
	return (qurey(lc(p),nl,nr)+qurey(rc(p),nl,nr))%mod;
}
//树链剖分
void dfs1(int now,int fath){
	fa[now]=fath;
	dep[now]=dep[fath]+1;
	siz[now]=1;
	int maxson=-1;
	for(int i=0;i<e[now].size();i++){
		int v=e[now][i];
		if(v!=fath){
			dfs1(v,now);
			siz[now]+=siz[v];
			if(siz[v]>maxson){
				maxson=siz[v];
				son[now]=v;
			}
		}
	}
}
void dfs2(int now,int topf){
	id[now]=++cnt;
	at[cnt]=a[now];
	top[now]=topf;
	if(!son[now])	return ;
	dfs2(son[now],topf);
	for(int i=0;i<e[now].size();i++){
		int v=e[now][i];
		if(v!=fa[now]&&v!=son[now]){
			dfs2(v,v);
		}
	}
}
void updRange(int x,int y,int k){
	k%=mod;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])	swap(x,y);
		modify(1,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])	swap(x,y);
	modify(1,id[y],id[x],k);
}
void updSon(int x,int k){
	k%=mod;
	modify(1,id[x],id[x]+siz[x]-1,k);
}
int qRange(int x,int y){
	int ret=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])	swap(x,y);
		ret+=qurey(1,id[top[x]],id[x]);
		ret%=mod;
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])	swap(x,y);
	ret+=qurey(1,id[y],id[x]);
	ret%=mod;
	return ret;
}
int qSon(int x){
	return qurey(1,id[x],id[x]+siz[x]-1);
}
int main(){
	n=RIN,m=RIN,r=RIN,mod=RIN;
	foru(i,1,n)	a[i]=RIN;
	foru(i,1,n-1){
		int u=RIN,v=RIN;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(r,0);
	dfs2(r,r);
	build(1,1,n);
	while(m--){
		int op=RIN,x,y,z;
		switch(op){
			case 1:
				x=RIN,y=RIN,z=RIN;
				updRange(x,y,z);
				break;
			case 2:
				x=RIN,y=RIN;
				printf("%d\n",qRange(x,y));
				break;
			case 3:
				x=RIN,z=RIN;
				updSon(x,z);
				break;
			case 4:
				x=RIN;
				printf("%d\n",qSon(x));
				break;	
		}
	}
	return 0;
}

那如果题目要维护的边权怎么办呢?

有一个相当暴力的想法就是记录每个点所有相连的边,然后当作点信息维护

但这样暂且不说去重的问题,复杂度和维护难度也指数级上升

考虑到这是一棵树,有n-1个边和n个点,所以可以想到把边和点“捆绑销售”

显然,对于一条边,我们肯定不会瞎找一个扯不上关系的点“捆绑”,因为这样很难进行维护

我们应该选择一条边两端点中,较深的那一个 

为什么不选较浅的呢?设想如果一个父节点有相当多的子节点,因为父节点比子节点浅,所以这些边都连到了父节点上,然后GG

而如果选较深的点,也就是每个点存储的是从根的方向过来的边,那因为是树,很明显除了根节点以外,每个节点“捆绑”且仅“捆绑”一条边


解决了边权到点权的映射,让我们考虑维护

设查询x,y之间的边权,对于单点查询某个边的边权,我们只需要调用qurey函数,让左右端点均为dep[x]>dep[y]?x:y即可

对于区间修改,我们正常用树剖的updRange函数修改x到y路径上点的点权即可

吗?

思考最简单的情况,有一条1->2->3的“树”,我们修改从1->3路径上的边权,树剖的updRange更改了3个点的值,但很显然边只有2个

那么那个点多余了?LCA(x,y)

因为LCA很明显是x-y路径上最高的点,走路径时不会再走到比LCA更高的地方,又因为每个点记录的自己上方(更靠近根节点,更浅,也就是更高)的边,所以LCA记录的边是不会被走的

因此,对于区间修改,我们updRange x~y +1之后,还要updRange LCA(x,y) -1


代码

// Problem: P3038 [USACO11DEC]Grass Planting G
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3038
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#include <bits/extc++.h>
#define INF 0x7fffffff
#define MAXN 200005
#define MAXM 100003
#define eps 1e-9
#define foru(a,b,c)	for(int a=b;a<=c;a++)
#define ford(a,b,c)	for(int a=b;a>=c;a--)
#define RT return 0;
#define db(x)	cout<<endl<<x<<endl;
#define LL long long
#define LXF int
#define RIN rin()
#define HH printf("\n")
using namespace std;
inline LXF rin(){
	LXF x=0,w=1;
	char ch=0;
	while(ch<'0'||ch>'9'){ 
	if(ch=='-') w=-1;
	ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
	x=x*10+(ch-'0');
	ch=getchar();
	}
	return x*w;
}
inline void out(LXF x){
	if(x<0){
	x=-x;
	putchar('-');
	}
	if(x>9) out(x/10);
	putchar(x%10+'0');
}
int n,m,a[MAXN],id[MAXN],dep[MAXN],top[MAXN],fa[MAXN],siz[MAXN],son[MAXN],cnt,ecnt,head[MAXN];
struct E{
	int v,nxt;
}e[MAXM<<1];
void add_e(int u,int v){
	e[++ecnt]=(E){v,head[u]};
	head[u]=ecnt;
}
void dfs1(int s,int fath){
	fa[s]=fath;
	siz[s]=1;
	dep[s]=dep[fath]+1;
	int maxson=-1;
	for(int i=head[s];i;i=e[i].nxt){
		int v=e[i].v;
		if(v!=fath){
			dfs1(v,s);
			siz[s]+=siz[v];
			if(siz[v]>maxson){
				maxson=siz[v];
				son[s]=v;
			}
		}
	}
}
void dfs2(int s,int topf){
	id[s]=++cnt;
	top[s]=topf;
	if(!son[s])	return ;
	dfs2(son[s],topf);
	for(int i=head[s];i;i=e[i].nxt){
		int v=e[i].v;
		if(v!=fa[s]&&v!=son[s]){
			dfs2(v,v);
		}
	}
}
int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])	swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])	swap(x,y);
	return y;
}
//SegTree
struct Segtree{
	int l,r,sum,lz;
}tr[MAXN<<2];
inline int lc(int x){return x<<1;}
inline int rc(int x){return 1+(x<<1);}
void push_up(int p){
	tr[p].sum=tr[lc(p)].sum+tr[rc(p)].sum;
}
void update(int p,int k){
	tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
	tr[p].lz+=k;
}
void push_down(int p){
	update(lc(p),tr[p].lz);
	update(rc(p),tr[p].lz);
	tr[p].lz=0;
}
void build(int p,int l,int r){
	tr[p].l=l,tr[p].r=r;
	if(l==r)	return ;
	int mid=(l+r)>>1;
	build(lc(p),l,mid);
	build(rc(p),mid+1,r);
	push_up(p);
}
void modify(int p,int nl,int nr,int k){
	int l=tr[p].l,r=tr[p].r;
	if(r<nl||nr<l)	return ;
	if(nl<=l&&r<=nr){
		update(p,k);
		return ;
	}
	push_down(p);
	modify(lc(p),nl,nr,k);
	modify(rc(p),nl,nr,k);
	push_up(p);
}
int qurey(int p,int nl,int nr){
	int l=tr[p].l,r=tr[p].r;
	if(r<nl||nr<l)	return 0;
	if(nl<=l&&r<=nr)	return tr[p].sum;
	push_down(p);
	return qurey(lc(p),nl,nr)+qurey(rc(p),nl,nr);
}
void updRange(int x,int y,int k){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])	swap(x,y);
		modify(1,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y])	swap(x,y);
	modify(1,id[y],id[x],k);
}
int main(){
	n=RIN,m=RIN;
	foru(i,1,n-1){
		int u=RIN,v=RIN;
		add_e(u,v);
		add_e(v,u);
	}
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	while(m--){
		char op;
		int x,y;
		cin>>op>>x>>y;
		if(op=='P'){
			updRange(x,y,1);
			int lca=LCA(x,y);
			updRange(lca,lca,-1);
		}else{
			printf("%d\n",qurey(1,id[(dep[x]>dep[y]?x:y)],id[(dep[x]>dep[y]?x:y)]));
		}
	}
	return 0;
}

 

posted @   Cap1taL  阅读(178)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示