树链剖分题目大集
树链剖分主要是解决对于树上路径的修改和查询操作,常常结合线段树来出题,使用线段树维护区间的属性(例如区间和,区间最值等问题)
如果还没学树链剖分的伙伴可以跳转到下面的博客
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;
}
如果有不清楚的地方,欢迎到评论区留言