树链剖分题目大集

树链剖分主要是解决对于树上路径的修改和查询操作,常常结合线段树来出题,使用线段树维护区间的属性(例如区间和,区间最值等问题)

如果还没学树链剖分的伙伴可以跳转到下面的博客

dfs序+树链剖分,超详细讲解+原理分析+模板(看不懂来打我

1.模板题 P3384 【模板】轻重链剖分/树链剖分

题目:https://www.luogu.com.cn/problem/P3384

分析:我们只需要用线段树+树链剖分来完成链或区间的修改和查询操作即可,线段树来维护区间和.

AC代码:

/*轻重链剖分/树链剖分*/
#include<bits/stdc++.h>
#define ls dep<<1
#define rs dep<<1|1
using namespace std;
const int Maxn = 1e5+10;
int N,M,R,P;
struct Edge{
	int  v;//节点编号 
	int next;
}edge[Maxn*2];
int head[Maxn];
int fa[Maxn];//记录父节点 
int dep[Maxn];//记录节点深度 
int son[Maxn];//记录重儿子
int siz[Maxn];//记录以该节点为根节点的树的大小(节点个数包括根节点) 
int top[Maxn];//每一个节点所属重链的根节点 
int dfn[Maxn];//每一个节点的时间戳
int w[Maxn];//dfs序后节点的权值,用线段树维护

int sum[Maxn*4];//线段树区间数组维护w[]区间和 
int lazy[Maxn*4];//维护区间加的延迟数据,以便于延迟下方 
int lpos[Maxn*4],rpos[Maxn*4];//线段树区间的左右端点 
int tim = 0;//时间戳计数器
int cnt = 0;
int v[Maxn];//存放所有节点的权值 
/*链式前向星建图,双向边*/
void build(int u,int v){
	edge[++cnt].v = v;
    edge[cnt].next = head[u];
    head[u] = cnt;
    edge[++cnt].v = u;
    edge[cnt].next = head[v];
    head[v] = cnt;
}
/*报一遍dfs记录重儿子,深度,以及子树大小*/
void dfs1(int u,int f){
	fa[u] = f;
	dep[u] = dep[f]+1;
	siz[u] = 1;
	int maxsonsize = -1;//记录重儿子的大小
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v=edge[i].v;//与u有边的节点 
		if(v==f) continue;//如果v时u的父亲,直接跳过
		/*否则就是u的儿子,dfs下去*/
		dfs1(v,u);
		siz[u]+=siz[v];
		/*更新u的重儿子*/
		if(siz[v]>maxsonsize){
			maxsonsize = siz[v];
			son[u] = v;
		}
	} 
}

/*再跑一边dfs,完成树链剖分*/
void dfs2(int u,int t){
	dfn[u] = ++tim;//dfs序 
	top[u] = t;//u所属重链的祖先节点
	w[tim] = v[u];//dfs序后的节点权值
	/*没有重儿子,代表时根节点,直接return*/
	if(!son[u]) return ;
	dfs2(son[u],t);
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		//如果v时u的父节点,或者v时u的重儿子(已经遍历过了) 
		if(v==fa[u]||v==son[u]) continue;
		/*否则以该节点为新的重链的祖先,继续dfs序*/
		else dfs2(v,v); 
	}
	return ;
}

/*更新区间和*/
void pushup(int dep){
	sum[dep] = sum[ls]%P+sum[rs]%P;
	sum[dep]%=P;
	return ;
}
/*延迟更新,标记下放操作*/
void pushdown(int dep){
	if(lazy[dep]){
		lazy[rs]+=lazy[dep];
		lazy[ls]+=lazy[dep];
		lazy[rs]%=P;
		lazy[ls]%=P;
		sum[ls]+=lazy[dep]%P*(rpos[ls]-lpos[ls]+1)%P;
		sum[rs]+=lazy[dep]%P*(rpos[rs]-lpos[rs]+1)%P;
		sum[ls]%=P;
		sum[rs]%=P;
		lazy[dep] = 0;
	}
	return ;
}
/*建立线段树*/
void build_tree(int l,int r,int dep){
	if(l==r){
		sum[dep] = w[l]%P;
		sum[dep]%=P;
		lpos[dep]=rpos[dep]=l;
		return ;
	}
	int mid = l+r>>1;
	build_tree(l,mid,ls);
	build_tree(mid+1,r,rs);
	lpos[dep] = l;
	rpos[dep] = r;
	pushup(dep);//更新区间和 
}
void modify(int l,int r,int ql,int qr,int dep,int val){
	if(ql<=l&&r<=qr){
		sum[dep]+=(r-l+1)%P*val%P;
		sum[dep]%=P;
		lazy[dep]+=val%P;
		lazy[dep]%=P;
		return ;
	}
	pushdown(dep);
	int mid = l+r>>1;
	if(ql<=mid) modify(l,mid,ql,qr,ls,val);
	if(qr>mid) modify(mid+1,r,ql,qr,rs,val);
	pushup(dep);
	return ;
}
/*区间查询*/
int query(int l,int r,int ql,int qr,int dep){
	if(ql<=l&&r<=qr){
		return sum[dep]%P;
	}
	pushdown(dep);
	int mid = l+r>>1;
	int ans = 0;
	if(ql<=mid) ans+=query(l,mid,ql,qr,ls);
	if(qr>mid) ans+=query(mid+1,r,ql,qr,rs);
	ans%=P;
	return ans;
}
/*初始化函数*/
void init(){
	memset(head,-1,sizeof(head));
	return ;
}
/*操作1:x-y链上的所有节点加上z*/
void modifychain(int x,int y,int z){
	z%=P;//因为答案要模P,所以先对z取模 
	
	/*节点x和y不在同一条重链上*/
	while(top[x]!=top[y]){
		/*先修改重链祖先更深的点,这样才能不断向上走合并为,
		转移到一条重链上最后(有点抽象,画个图)*/
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		modify(1,N,dfn[top[x]],dfn[x],1,z);//区间修改
		x = fa[top[x]];	 
	}
	if(dep[x]>dep[y]) swap(x,y);
	modify(1,N,dfn[x],dfn[y],1,z);
	return ;
}
/*操作2:树从 x 到 y 结点最短路径上所有节点的值之和*/
int querychain(int x,int y){
	int ans = 0;
	/*节点x和y不在同一条重链上*/
	while(top[x]!=top[y]){
		/*先修改重链祖先更深的点,这样才能不断向上走合并为,
		转移到一条重链上最后(有点抽象,画个图)*/
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=query(1,N,dfn[top[x]],dfn[x],1);//区间修改
		ans%=P;
		x = fa[top[x]];	 
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans+=query(1,N,dfn[x],dfn[y],1);
	ans%=P;
	return ans;
}
/*操作3:以x为根节点的子数内所有节点值都加上z*/
void modifyson(int x,int z){
	/*因为一个节点的子树上的时间戳一定大于该节点,并且连续*/
	modify(1,N,dfn[x],dfn[x]+siz[x]-1,1,z);//线段树维护的w[]数组的区间修改 
} 
/*操作4:求以x为根节点的子数内所有节点值之和*/
int queryson(int x){
	return query(1,N,dfn[x],dfn[x]+siz[x]-1,1)%P;
}
/*
void t_sum(int l,int r,int dep){
	if(l==r){
		cout<<sum[dep]<<' '<<dep<<'\n';
		return ;
	}
	int mid = l+r>>1;
	t_sum(l,mid,ls);
	t_sum(mid+1,r,rs);
	return ;
}*/
int main()
{
	init();
	cin>>N>>M>>R>>P;//表示树的结点个数、操作个数、根节点序号和取模数
	for(int i=1;i<=N;i++) cin>>v[i];
    for(int i=1;i<N;i++){
    	int u,v;
    	cin>>u>>v;
    	build(u,v);
	}	
	dfs1(R,R);
	dfs2(R,R);
	build_tree(1,N,1);
	for(int i=1;i<=M;i++){
		int op;
		cin>>op;
		if(op==1){
			int x,y,z;
			cin>>x>>y>>z;
			modifychain(x,y,z);
		}
		else if(op==2){
			int x,y;
			cin>>x>>y;
			cout<<querychain(x,y)%P<<'\n';
		}
		else if(op==3){
			int x,z;
			cin>>x>>z;
			modifyson(x,z);
		}
		else if(op==4){
			int x;
			cin>>x;
			cout<<queryson(x)%P<<'\n';	
		}
	}
	return 0;
}


2.洛谷-P3379 【模板】最近公共祖先(LCA)(强行用树剖写LCA)

题目:https://www.luogu.com.cn/problem/P3379

分析:首先用树链剖分将X,Y点合并到一条重链上,最后结点深度小的(也就是在上面的)即为他们的LCA

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 5e5+10;
struct Edge{
	int v;
	int next;	
}edge[Maxn*2];
int head[Maxn];
int cnt = 0;
/*链式前向星建图*/
void build(int u,int v){
   edge[++cnt].v=v;
   edge[cnt].next=head[u];
   head[u] = cnt;
   edge[++cnt].v = u;
   edge[cnt].next = head[v];
   head[v] = cnt;
   return ;
}
int fa[Maxn];//父节点 
int son[Maxn];//重儿子 
int siz[Maxn];//树的大小
int dep[Maxn];//节点深度
int dfn[Maxn];//节点的时间戳
int top[Maxn];//每一条重链的根节点
int tim;//时间戳计数器
int N,M,S;
//第一次dfs找出重儿子,节点深度,树的大小 
void dfs1(int u,int f){
	fa[u] = f;
	dep[u] = dep[f]+1;
	siz[u] = 1;
	int maxsonsize = -1;
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		/*如果v是u的父节点,continue*/
		if(v==f) continue;
		/*否则就是u的儿子*/
		dfs1(v,u);
		siz[u]+=siz[v];//更新u节点的大小
		/*更新u节点的重儿子*/
		if(siz[v]>maxsonsize){
		  maxsonsize = siz[v];
		  son[u] = v;	
		}  
	}
	return ; 
} 
/*跑第二遍dfs,完成树链剖分,轻重链剖分
找出节点时间戳,重链的祖先节点*/
void dfs2(int u,int t){
	dfn[u] = ++tim;
	top[u] = t;
	//cout<<u<<' '<<t<<'\n';
	/*u没有重儿子*/
	if(!son[u]) return ;
	
	dfs2(son[u],t);
	for(int i=head[u];i!=-1;i=edge[i].next){
		
		int v = edge[i].v;
		//cout<<u<<' '<<v<<'\n';
		/*如果v是u的父亲continue,如果v是u的重儿子(已经dfs过continue)*/
		if(v==fa[u]||v==son[u]) continue;
		/*否则v是u的轻儿子之一,以这个节点作为新重链的祖先节点*/
		dfs2(v,v);
	}
	return ;	
}
/*找x和y的最近公共祖先*/
void solve(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]) cout<<x;
	else cout<<y;
	cout<<'\n';
	return ; 
}
void init(){
	memset(head,-1,sizeof(head));
	return ;
}

int main(){
	init();
	cin>>N>>M>>S;//分别表示树的结点个数、询问的个数和树根结点的序号
	for(int i=1;i<N;i++){
		int u,v;
		cin>>u>>v;
		build(u,v);
	}
	//cout<<1<<'\n';
	dfs1(S,S);
	dfs2(S,S);
	//cout<<top[2];
	//cout<<1<<'\n';
	for(int i=1;i<=M;i++){
		int x,y;
		cin>>x>>y;
		solve(x,y);
	}
	return 0;
} 

3.洛谷-P2590 [ZJOI2008]树的统计

题目:https://www.luogu.com.cn/problem/P2590

分析:这道题也是树上的单点修改加区间查询,树链剖分+线段树,不过线段树需要维护两个值,一个是区间和,一个是区间最大值

AC代码:

#include<bits/stdc++.h>
#define ls dep<<1
#define rs dep<<1|1
using namespace std;
const int Maxn = 3e4+10;
struct Edge{
	int v;
	int next;
}edge[Maxn*2];
struct node{
	int maxx;
	int sum;
}tr[Maxn*4];
int head[Maxn];
int fa[Maxn];
int son[Maxn];
int dep[Maxn];
int dfn[Maxn];
int top[Maxn];
int siz[Maxn];
int rnk[Maxn];
int val[Maxn];
int n;
int cnt;
int tim;
void build(int u,int v){
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
	edge[++cnt].v = u;
	edge[cnt].next = head[v];
	head[v] = cnt;
	return ;
}
void dfs1(int u,int f){
	fa[u] = f;
	dep[u] = dep[f]+1;
	siz[u] = 1;
	int maxsonsize = -1;
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==f) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxsonsize){
			maxsonsize = siz[v];
			son[u] = v;
		}
	}
	return ;
}
void dfs2(int u,int t){
	top[u] = t;
	dfn[u] = ++tim;
	rnk[tim] = u;
	if(!son[u]) return ;
	dfs2(son[u],t);
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
	return ;
}
void pushup(int dep){
	tr[dep].maxx = max(tr[ls].maxx,tr[rs].maxx);
	tr[dep].sum = tr[ls].sum+tr[rs].sum;
	return ;
}
void build_tree(int l,int r,int dep){
	if(l==r){
		tr[dep].maxx = val[rnk[l]];
		tr[dep].sum = val[rnk[l]];
		return ;
	}
	int mid = l+r>>1;
	build_tree(l,mid,ls);
	build_tree(mid+1,r,rs);
	pushup(dep);
	return ;
}
/*单点修改*/
void modify(int l,int r,int ql,int qr,int dep,int val){
	if(ql<=l&&r<=qr){
		tr[dep].maxx = tr[dep].sum = val;
		return ;
	}
	int mid = l+r>>1;
	if(ql<=mid) modify(l,mid,ql,qr,ls,val);
	if(qr>mid) modify(mid+1,r,ql,qr,rs,val);
	pushup(dep);
	return ;
}
/*询问区间最大值*/
int query1(int l,int r,int ql,int qr,int dep){
	if(ql<=l&&r<=qr){
		return tr[dep].maxx;
	}
	int mid = l+r>>1;
	int ans = -1e9;
	if(ql<=mid) ans = max(ans,query1(l,mid,ql,qr,ls));
	if(qr>mid) ans = max(ans,query1(mid+1,r,ql,qr,rs));
	return ans;
}
/*询问区间和*/
int query2(int l,int r,int ql,int qr,int dep){
	if(ql<=l&&r<=qr){
		return tr[dep].sum;
	}
	int mid = l+r>>1;
	int ans = 0;
	if(ql<=mid) ans+=query2(l,mid,ql,qr,ls);
	if(qr>mid) ans+=query2(mid+1,r,ql,qr,rs);
	return ans;
}
int qchain1(int x,int y){
	int ans = -1e9;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans = max(ans,query1(1,n,dfn[top[x]],dfn[x],1));
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans = max(ans,query1(1,n,dfn[x],dfn[y],1));
	return ans; 	 
}
int qchain2(int x,int y){
	int ans = 0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=query2(1,n,dfn[top[x]],dfn[x],1);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans+=query2(1,n,dfn[x],dfn[y],1);
	return ans;
}
void init(){
	cnt = 0;
	tim = 0;
	memset(head,-1,sizeof(head));
	return ;		
}
int main()
{
	init();
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		build(u,v);
	}
	for(int i=1;i<=n;i++) cin>>val[i];
	dfs1(1,1);
	dfs2(1,1);
	build_tree(1,n,1);
	int q;
	cin>>q;
	for(int i=1;i<=q;i++){
		char s[10];
		int u,t,v;
		cin>>s;
		if(s[1]=='H'){
			cin>>u>>t;
			modify(1,n,dfn[u],dfn[u],1,t);
		}
		else if(s[1]=='M'){
			cin>>u>>v;
			int ans = qchain1(u,v);
			cout<<ans<<'\n';
		}
		else{
			cin>>u>>v;
			int ans = qchain2(u,v);
			cout<<ans<<'\n';
		}
		
	}
	return 0;
}

4.P3038 [USACO11DEC]Grass Planting G

题目:https://www.luogu.com.cn/problem/P3038

分析:这道题稍微有一点不一样,这道题是对路径的边权进行修改,查询边的权值,所以我们线段树要维护的是边权,但是边权不太好维护,所以我们需要把边权转化为点权即可,边权转点权也很简单,一条边有两个节点,我们把儿子节点的点权作为这条边的边权即可,显然根节点是没有权值的.

AC代码:

#include<bits/stdc++.h>
#define ls dep<<1
#define rs dep<<1|1
using namespace std;
const int Maxn = 1e5+10;
struct Edge{
	int v;
	int next;
}edge[Maxn*2];
struct node{
	int lz;
	int sum;
}tr[Maxn*4];
int head[Maxn];
int fa[Maxn];
int son[Maxn];
int dep[Maxn];
int dfn[Maxn];
int top[Maxn];
int siz[Maxn];
int cnt,tim;
int n,m;
void build(int u,int v){
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
	edge[++cnt].v = u;
	edge[cnt].next = head[v];
	head[v] = cnt;
	return ;
}
void dfs1(int u,int f){
	dep[u] = dep[f]+1;
	siz[u] = 1;
	fa[u] = f;
	int maxsonsize = -1;
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==f) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxsonsize){
			maxsonsize = siz[v];
			son[u] = v;
		}
	}
	return ;
}
void dfs2(int u,int t){
	top[u] = t;
	dfn[u] = ++tim;
	if(!son[u]) return ;
	dfs2(son[u],t);
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
	return ;
}
void pushup(int dep){
	tr[dep].sum = tr[ls].sum+tr[rs].sum;
	return ;	
}
void pushdown(int dep){
	if(tr[dep].lz){
		tr[ls].sum += tr[dep].lz;
		tr[rs].sum += tr[dep].lz;
		tr[ls].lz+=tr[dep].lz;
		tr[rs].lz+=tr[dep].lz; 
		tr[dep].lz = 0;
	}
	return ;
}
void build_tree(int l,int r,int dep){
	tr[dep].lz = 0;
	if(l==r){
		tr[dep].sum = 0;
		return ;
	}
	int mid = l+r>>1;
	build_tree(l,mid,ls);
	build_tree(mid+1,r,rs);
	pushup(dep);
	return ;
}
void modify(int l,int r,int ql,int qr,int dep,int val){
	if(ql<=l&&r<=qr){
		tr[dep].lz+=val;
		tr[dep].sum+=val;
		return ;
	}
	pushdown(dep);
	int mid = l+r>>1;
	if(ql<=mid) modify(l,mid,ql,qr,ls,val);
	if(qr>mid) modify(mid+1,r,ql,qr,rs,val);
	return ;
}
void mchain(int x,int y,int val){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);	
		else modify(1,n,dfn[top[x]],dfn[x],1,val);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	modify(1,n,dfn[x]+1,dfn[y],1,val);//LCA这个点不做修改,思考为什么? 
	return ;
}
int query(int l,int r,int ql,int qr,int dep){
	if(ql<=l&&r<=qr){
		return tr[dep].sum;
	}
	pushdown(dep);
	int mid = l+r>>1;
	int ans = 0;
	if(ql<=mid) ans+=query(l,mid,ql,qr,ls);
	if(qr>mid) ans+=query(mid+1,r,ql,qr,rs);
	return ans;
}
void init(){
	cnt = tim = 0;
	memset(head,-1,sizeof(head));
	return ;
}
int main(){
	init();
	cin>>n>>m;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		build(u,v);
	}
	dfs1(1,1);
	dfs2(1,1);
	build_tree(1,n,1);
	for(int i=1;i<=m;i++){
		char s[10];
		int a,b;
		cin>>s>>a>>b;
		if(s[0]=='P'){
		    mchain(a,b,1);
		}
		else{
			int x,y;
			x=y=max(dfn[a],dfn[b]);//查询谁是子节点 
			int ans = query(1,n,x,y,1);
			cout<<ans<<'\n';
		}
	} 
}

5.P3178 [HAOI2015]树上操作

题目:https://www.luogu.com.cn/problem/P3178

分析:这道题的考察点就是树链剖分后对于一棵子树,它的dfs序一定是连续的,所以对于操作2修改以x为根节点的子树的所有节点的值就会用到这个性质,还要注意的是开long long.

AC代码:

#include<bits/stdc++.h>
#define ls dep<<1
#define rs dep<<1|1
using namespace std;
typedef long long ll;
const int Maxn = 1e5+10;
struct Edge{
	ll v;
	ll next;
}edge[Maxn*2];
struct node{
	ll lz;
	ll sum;
}tr[Maxn*4];
ll n,m;
ll head[Maxn];
ll fa[Maxn];
ll son[Maxn];
ll dep[Maxn];
ll dfn[Maxn];
ll top[Maxn];
ll siz[Maxn];
ll rnk[Maxn];
ll val[Maxn];
ll cnt,tim;
void pushup(ll dep){
	tr[dep].sum = tr[ls].sum+tr[rs].sum;
	return ;
}
void pushdown(ll dep,ll l,ll r){
	if(tr[dep].lz){
		ll mid = l+r>>1;
		ll temp = tr[dep].lz;
		tr[ls].lz+=temp;
		tr[rs].lz+=temp;
		tr[ls].sum+=(mid-l+1)*temp;
		tr[rs].sum+=(r-mid)*temp;
		tr[dep].lz = 0;
	}
	return ;
}
void build(ll u,ll v){
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
	edge[++cnt].v = u;
	edge[cnt].next = head[v];
	head[v] = cnt;
	return ;
}
void dfs1(ll u,ll f){
	fa[u] = f;
	siz[u] = 1;
	dep[u] = dep[f]+1;
	ll maxsonsize = -1;
	for(ll i=head[u];i!=-1;i=edge[i].next){
		ll v = edge[i].v;
		if(v==f) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxsonsize){
			maxsonsize = siz[v];
			son[u] = v;
		}
	}
	return ;
}
void dfs2(ll u,ll t){
	top[u] = t;
	dfn[u] = ++tim;
	rnk[tim] = u;
	if(!son[u]) return ;
	dfs2(son[u],t);
	for(ll i=head[u];i!=-1;i=edge[i].next){
		ll v=edge[i].v;
	    if(v==fa[u]||v==son[u]) continue;
	    dfs2(v,v);
	}
	return ;
}
void build_tree(ll l,ll r,ll dep){
	tr[dep].lz = 0;
	if(l==r){
		tr[dep].sum = val[rnk[l]];
		return ;
	}
	ll mid = l+r>>1;
	build_tree(l,mid,ls);
	build_tree(mid+1,r,rs);
	pushup(dep);
	return ;
}
void modify(ll l,ll r,ll ql,ll qr,ll dep,ll val){
	if(ql<=l&&r<=qr){
		tr[dep].lz+=val;
		tr[dep].sum+=(r-l+1)*val;
		return ;
	}
	pushdown(dep,l,r);
	ll mid = l+r>>1;
	if(ql<=mid) modify(l,mid,ql,qr,ls,val);
	if(qr>mid) modify(mid+1,r,ql,qr,rs,val);
	pushup(dep);
	return ;
}
ll query(ll l,ll r,ll ql,ll qr,ll dep){
	if(ql<=l&&r<=qr){
		return tr[dep].sum;
	}
	pushdown(dep,l,r);
	ll mid = l+r>>1;
	ll ans = 0;
	if(ql<=mid) ans+=query(l,mid,ql,qr,ls);
	if(qr>mid) ans+=query(mid+1,r,ql,qr,rs);
	pushup(dep);
	return ans; 
}
ll qchain(ll x,ll y){
	ll ans = 0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=query(1,n,dfn[top[x]],dfn[x],1);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans+=query(1,n,dfn[x],dfn[y],1);
	return ans;
}
void init(){
	cnt = tim = 0;
	memset(head,-1,sizeof(head));
}
int main(){
	init();
	cin>>n>>m;
	for(ll i=1;i<=n;i++) cin>>val[i];
	for(ll i=1;i<n;i++){
		ll u,v;
		cin>>u>>v;
		build(u,v);
	}
	dfs1(1,1);
	dfs2(1,1);
	build_tree(1,n,1);
	for(ll i=1;i<=m;i++){
		ll op;
		cin>>op;
		if(op==1){
			ll x,a;
			cin>>x>>a;
			modify(1,n,dfn[x],dfn[x],1,a);
		}
		else if(op==2){
			ll x,a;
			cin>>x>>a;
			modify(1,n,dfn[x],dfn[x]+siz[x]-1,1,a);
		}
		else{
			ll x;
			cin>>x;
			ll ans = qchain(1,x);
			cout<<ans<<'\n';
		}
	}
	return 0;
}

6.树链剖分+线段树 2019ICPC西安邀请赛E

题目:https://nanti.jisuanke.com/t/39272

题解:https://blog.csdn.net/TheWayForDream/article/details/118580366

7.HDU - 5052

题目:https://vjudge.net/problem/HDU-5052

题目大意:给你一棵树,树的每一个节点带有初始权值(会给你),然后你有m次操作,操作情况如下
操作:给你X,Y,V;
从点X到点Y的路径上,你可以在一个点买入一个鸡(价格是这个点的权值),然后在弄一个点卖出这只鸡(价格也是这个点的权值),然后问你最大收益,如果赚不了钱,输出0(也就是你可以选择不买也不卖,逛逛集市看热闹),注意:先买后卖,并且X到Y一条路周到底不能返回,也就是买入点要在卖出点之前,最后把这条路的所有点权+V

分析:这里的路径是有顺序的,从X到Y和从Y到X是反向的,所以我们需要维护一个链从上倒下的最大收益,和从下到上的最大收益两个方向,最后通过区间合并求出从X到Y的最大收益即可

AC代码:

#include<iostream>
#include<cstring>
#include<cmath>
#include<vector>
#define ls dep<<1
#define rs dep<<1|1
using namespace std;
const int Maxn = 5e4+10;
struct Edge{
	int v;
	int next;
}edge[Maxn*2];
struct node{
	int lz;
	int maxx;
	int minn;
	int upon;
	int down;
	node(){	
	lz = maxx = minn = upon = down=0;
	}
}tr[Maxn*4];
int N,M;
int head[Maxn];
int top[Maxn];
int dep[Maxn];
int dfn[Maxn];
int son[Maxn];
int siz[Maxn];
int fa[Maxn];
int rnk[Maxn];
int a[Maxn];
int tim;
int cnt;
/*链式前向星建图*/
void build(int u,int v){
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
	edge[++cnt].v = u;
	edge[cnt].next = head[v];
	head[v] = cnt;
	return ;
}
/*树剖的第一遍dfs*/
void dfs1(int u,int f){
	son[u] = 0;
	fa[u] = f;
	dep[u] = dep[f]+1;
	siz[u] = 1;
	int maxsonsize = -1;
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==f) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxsonsize){
			maxsonsize = siz[v];
			son[u] = v;
		}
	}
	return ;
}
/*树剖的第二遍dfs*/
void dfs2(int u,int t){
	dfn[u] = ++tim;
	rnk[tim] = u;
	top[u] = t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v=edge[i].v;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
	return ;
}
/*线段树的区间合并,注意这里链的合并是有顺序的,左边是左儿子
右边是右儿子,如果搞反了那就麻烦了*/
node merge(node a,node b){
	node ans = node();
	ans.maxx = max(a.maxx,b.maxx);
	ans.minn = min(a.minn,b.minn);
	ans.upon = max(a.upon,b.upon);
	ans.upon = max(ans.upon,a.maxx-b.minn);//从b到a也就是从下到上 
	ans.down = max(a.down,b.down);
	ans.down = max(ans.down,b.maxx-a.minn);//从a到b也就是从上倒下 
	return ans;
}
/*建线段树*/
void build_tree(int l,int r,int dep){
	tr[dep].lz = 0;
	if(l==r){
		tr[dep].maxx = tr[dep].minn = a[rnk[l]];
		return ;
	}
	int mid = l+r>>1;
	build_tree(l,mid,ls);
	build_tree(mid+1,r,rs);
	tr[dep] = merge(tr[ls],tr[rs]);
	return ;
}
/*标记下方*/
void pushdown(int dep){
	if(tr[dep].lz){
		int v = tr[dep].lz;
		tr[ls].lz+=v;
		tr[rs].lz+=v;
		tr[ls].maxx+=v;
		tr[ls].minn+=v;
		tr[rs].maxx+=v;
		tr[rs].minn+=v;
		tr[dep].lz = 0;
	}
	return ;
}
/*线段树区间修改*/
void modify(int l,int r,int ql,int qr,int dep,int val){
	if(ql<=l&&r<=qr){
		tr[dep].lz+=val;
		tr[dep].maxx+=val;
		tr[dep].minn+=val;
		return ;
	}
	pushdown(dep);
	int mid = l+r>>1;
	if(ql<=mid) modify(l,mid,ql,qr,ls,val);
	if(qr>mid) modify(mid+1,r,ql,qr,rs,val);
	tr[dep] = merge(tr[ls],tr[rs]);
	return ;
}
/*链的修改*/
void mchain(int x,int y,int val){
     while(top[x]!=top[y]){
     	if(dep[top[x]]<dep[top[y]]) swap(x,y);
     	modify(1,N,dfn[top[x]],dfn[x],1,val);
     	x = fa[top[x]];
	 }
	 if(dep[x]>dep[y]) swap(x,y);
	 modify(1,N,dfn[x],dfn[y],1,val);
	 return ;
}
/*线段树区间查询*/
node query(int l,int r,int ql,int qr,int dep){
	if(ql<=l&&r<=qr){
		return tr[dep];
	}
	pushdown(dep);
	int mid = l+r>>1;
	node sum[3];
	int num = 0;
	if(ql<=mid){
		sum[++num] = query(l,mid,ql,qr,ls);
	} 
	if(qr>mid){
		sum[++num] = query(mid+1,r,ql,qr,rs);
	}
	tr[dep] = merge(tr[ls],tr[rs]);
	node ans = node();
	if(num==2){
		ans = merge(sum[1],sum[2]);
    }
    else ans = sum[1];
    return ans;
}
/*链的查询*/
int qchain(int x,int y,int val){
	node ans1 = node(),ans2 = node();
	int flag1 =0,flag2 = 0;
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]]){
			if(flag1==0){
				ans1 = query(1,N,dfn[top[x]],dfn[x],1);
				flag1++;
			}
			else{
				/*注意区间合并有顺序,搞清楚哪个应该是左儿子,哪个是右儿子*/
				ans1 = merge(query(1,N,dfn[top[x]],dfn[x],1),ans1);
			}
			x=fa[top[x]];
		}
		else{
			if(flag2==0){
				ans2 = query(1,N,dfn[top[y]],dfn[y],1);
				flag2++;
			}
			else{
				ans2 = merge(query(1,N,dfn[top[y]],dfn[y],1),ans2);
			}
				y=fa[top[y]];
		}
	}
	int res = 0;
	if(dep[x]<=dep[y]){
		node ans3 = query(1,N,dfn[x],dfn[y],1);
		res = ans3.down;
		if(flag2){
			ans3 = merge(ans3,ans2);
			res = max(res,ans3.down);
		}
		if(flag1){
			res = max(res,max(ans1.upon,ans3.down));
			res = max(res,ans3.maxx-ans1.minn);
		}
	}
	else{
		node ans3 = query(1,N,dfn[y],dfn[x],1);
		res = ans3.upon;
		if(flag1){
			ans3 = merge(ans3,ans1);
			res = max(res,ans3.upon);
		}
		if(flag2){
			res = max(res,max(ans3.upon,ans2.down));
			res = max(res,ans2.maxx-ans3.minn);
		}
	}
	return res;
}
void init(){
	cnt = 0;
	tim = 0;
	memset(head,-1,sizeof(head));
	return ;
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		init();
		scanf("%d",&N);
		for(int i=1;i<=N;i++) scanf("%d",&a[i]);
		for(int i=1;i<N;i++){
			int u,v;
			scanf("%d %d",&u,&v);
			build(u,v);
		}
		dfs1(1,1);
		dfs2(1,1);
		build_tree(1,N,1);
		scanf("%d",&M);
		for(int i=1;i<=M;i++){
			int x,y,val;
			scanf("%d %d %d",&x,&y,&val);
			int ans = qchain(x,y,val);
			mchain(x,y,val);
			printf("%d\n",ans);	
		}
	}
	return 0;
}

如果有不清楚的地方,欢迎到评论区留言

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值