线段树合并学习笔记
前置芝士:线段树动态开点
使用场景:
- 维护区间太大,
存不下,通常是值域线段树。 - 维护的区间下标存在负数。
空间复杂度:
- 全部开点,则
- 每递归一次,最多开点
,若调用 次就是 。
原理:
- 若一段子区间
对应的线段树节点为 ,当不需要递归时,就不建点。 - 当调用
addtag()
时,新建节点。
注意事项:
- 没有
build()
的过程。都让你动态开点了怎么可能要build()
啊喂 - 区间存在负数时,
应是lt+rt-1>>1
而不是lt+rt>>1
。(虽然正整数写这个也没关系) addtag()
的 加上取址符。- 根节点占一个,所以总结点数
的初始值为 。
模板代码(P3372)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int n,m,tot;
int a[N];
#define lc(x) tree[x].lc
#define rc(x) tree[x].rc
#define sum(x) tree[x].val
#define tag(x) tree[x].tag
struct node{
int val,tag,lc,rc;
}tree[N<<1];
void push_up(int cur){
tree[cur].val=tree[lc(cur)].val+tree[rc(cur)].val;
return;
}
void addtag(int &cur,int lt,int rt,int val){
if(!cur)cur=++tot;
tree[cur].tag+=val;
tree[cur].val+=(rt-lt+1)*val;
return;
}
void push_down(int cur,int lt,int rt){
if(lt>=rt)return;
int mid=(lt+rt-1)>>1;
addtag(lc(cur),lt,mid,tree[cur].tag);
addtag(rc(cur),mid+1,rt,tree[cur].tag);
tree[cur].tag=0;
return;
}
int query(int cur,int lt,int rt,int qx,int qy){
if(lt>qy||rt<qx)return 0;
if(qx<=lt&&qy>=rt)return tree[cur].val;
push_down(cur,lt,rt);
int mid=(lt+rt-1)>>1;
return query(lc(cur),lt,mid,qx,qy)+query(rc(cur),mid+1,rt,qx,qy);
}
void update(int cur,int lt,int rt,int qx,int qy,int val){
if(lt>qy||rt<qx)return;
if(qx<=lt&&qy>=rt){
addtag(cur,lt,rt,val);
return;
}
int mid=(lt+rt-1)>>1;
push_down(cur,lt,rt);
update(lc(cur),lt,mid,qx,qy,val);
update(rc(cur),mid+1,rt,qx,qy,val);
push_up(cur);
return;
}
signed main(){
cin>>n>>m;
tot=1;
for(int i=1;i<=n;i++){
cin>>a[i];
update(1,1,n,i,i,a[i]);
}
for(int i=1;i<=m;i++){
int kind;
cin>>kind;
if(kind==1){
int x,y,k;
cin>>x>>y>>k;
update(1,1,n,x,y,k);
}
else{
int x,y;
cin>>x>>y;
cout<<query(1,1,n,x,y)<<'\n';
}
}
return 0;
}
正题:线段树合并
使用场景:有多棵线段树,维护了相同的区间
板子
int merge(int a,int b,int l,int r){
if(!a||!b)return a+b;
if(l==r){
tree[a].val+=tree[b].val;
tree[a].pos=tree[a].val?l:0;
}
int mid=l+r-1>>1;
lc(a)=merge(lc(a),lc(b),l,mid);
rc(a)=merge(rc(a),rc(b),mid+1,r);
push_up(a,l,r);;
return a;
}
例题
看一下P4556。
可以注意到,对每个点开一个桶,加上树上查分即可。
但是时间比较劣。
所以对每一个开上权值线段树。
动态开点的话空间复杂度
然后从叶子结点开始向上
额外维护编号,自己发明一下新的 push_up()
即可。
因为是单调修改,不需要addtag()
和push_down()
,也就没有懒标记了。
查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,tot,dp[100005][21],d[100005],rt[100005];
#define lc(x) tree[x].lc
#define rc(x) tree[x].rc
#define sum(x) tree[x].val
#define tag(x) tree[x].tag
struct node{
int val,tag,lc,rc,pos;
}tree[10000005];
void push_up(int cur){
if(tree[lc(cur)].val>=tree[rc(cur)].val){
tree[cur].val=tree[lc(cur)].val;
tree[cur].pos=tree[lc(cur)].pos;
}
else{
tree[cur].val=tree[rc(cur)].val;
tree[cur].pos=tree[rc(cur)].pos;
}
return;
}
void update(int &cur,int lt,int rt,int qx,int qy,int val){
if(!cur)cur=++tot;
if(lt>qy||rt<qx)return;
if(qx<=lt&&qy>=rt){
tree[cur].val+=val;
tree[cur].pos=qx;
return;
}
int mid=(lt+rt-1)>>1;
update(lc(cur),lt,mid,qx,qy,val);
update(rc(cur),mid+1,rt,qx,qy,val);
push_up(cur);
return;
}
int merge(int a,int b,int l,int r){
if(!a||!b)return a+b;
if(l==r){
tree[a].val+=tree[b].val;
tree[a].pos=tree[a].val>0?l:0;
return a;
}
int mid=l+r-1>>1;
lc(a)=merge(lc(a),lc(b),l,mid);
rc(a)=merge(rc(a),rc(b),mid+1,r);
push_up(a);
return a;
}
vector<int>nbr[N];
void before(int cur,int fa){
d[cur]=d[fa]+1;
dp[cur][0]=fa;
for(int i=1;i<=20;++i)dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(auto to:nbr[cur]){
if(fa==to)continue;
before(to,cur);
}
return;
}
int LCA(int u,int v){
if(d[u]>d[v])swap(u,v);
for(int i=20;i>=0;i--){
if((1<<i)<=d[v]-d[u])v=dp[v][i];
}
if(u==v)return u;
for(int i=20;i>=0;i--){
if(dp[u][i]!=dp[v][i]){
u=dp[u][i];
v=dp[v][i];
}
}
return dp[u][0];
}
void upd(int x,int y,int z){
int lca=LCA(x,y);
update(rt[x],1,1e5,z,z,1);
update(rt[y],1,1e5,z,z,1);
update(rt[lca],1,1e5,z,z,-1);
update(rt[dp[lca][0]],1,1e5,z,z,-1);
return;
}
int ans[100005];
void dfs_(int cur,int fa){
for(auto to:nbr[cur]){
if(to==fa)continue;
dfs_(to,cur);
rt[cur]=merge(rt[cur],rt[to],1,1e5);
}
ans[cur]=tree[rt[cur]].pos;
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<n;++i){
int u,v;
cin>>u>>v;
nbr[u].push_back(v);
nbr[v].push_back(u);
}
before(1,0);
for(int i=1;i<=n;++i)rt[i]=++tot;
for(int i=1;i<=m;++i){
int x,y,z;
cin>>x>>y>>z;
upd(x,y,z);
}
dfs_(1,0);
for(int i=1;i<=n;++i)cout<<ans[i]<<'\n';
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战