线段树合并学习笔记

前置芝士:线段树动态开点

使用场景:

  • 维护区间太大,\(4\times N\) 存不下,通常是值域线段树。
  • 维护的区间下标存在负数。

空间复杂度:

  • 全部开点,则 \(2\times N -1\)
  • 每递归一次,最多开点 \(O(\log N)\),若调用 \(M\) 次就是 \(M\log N\)

原理:

  • 若一段子区间 \([L,R]\) 对应的线段树节点为 \(cur\),当不需要递归时,就不建点。
  • 当调用 addtag() 时,新建节点。

注意事项:

  • 没有 build() 的过程。都让你动态开点了怎么可能要 build() 啊喂
  • 区间存在负数时,\(mid\) 应是 lt+rt-1>>1 而不是 lt+rt>>1。(虽然正整数写这个也没关系)
  • addtag()\(cur\) 加上取址符。
  • 根节点占一个,所以总结点数 \(tot\) 的初始值为 \(1\)
模板代码(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;
}

正题:线段树合并

使用场景:有多棵线段树,维护了相同的区间 \([L,R]\),通常是权值线段树。每一棵线段树维护了区间内的最大值(区间元素和),\(M\) 次单点修改,每次修改一棵线段树的位置为 \(pos\) 的值,修改后所有线段树对应区间位置的权值相加并维护区间最大值。

板子
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。
可以注意到,对每个点开一个桶,加上树上查分即可。
但是时间比较劣。
所以对每一个开上权值线段树。
动态开点的话空间复杂度 \(O(n\log n)\),可以通过。
然后从叶子结点开始向上 \(\text{merge}\) 即可。
额外维护编号,自己发明一下新的 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;
}

posted @ 2023-01-12 21:10  Forever1507  阅读(23)  评论(0编辑  收藏  举报