线段树合并

线段树合并

前置芝士:

动态开点线段树和权值线段树:

因为只有动态开点线段树,在合并时才不用完整建一棵线段树

问题:

如果有若干个线段树,维护相同的值域,并且在某一棵线段树上进行单点修改操作。

最后,我们希望把这些线段树对应位置上的值相加,同时维护区间最大值。

这样,就用到了线段树合并:

思想:

就是建立一棵新的线段树,保存原有的两棵线段树的信息。

方法:

用两个指针 \(p,q\) 从两个根节点,以递归的方式同步遍历两棵线段树:

  • \(p,q\) 之一为空,则以非空的作为合并后的节点

  • \(p,q\) 都不为空,则递归合并两棵左子树和两棵右子树,然后删除节点 \(q\)\(p\) 为合并后的节点,从底向上更新最值信息。

若已经到达叶子节点,则直接把两个最值相加即可。

时间复杂度:

设有 \(m\) 次单点修改,维护值域 \([1,n]\) ,合并函数的执行次数不会超过所有线段树的节点总数 \(+1\)

因此,整个合并过程的时间复杂度为 \(O(m \log n)\)

代码:

int merge(int p,int q,int l,int r){
    if(!p) return q;
    if(!q) return p;
    if(l==r){
        t[p].maxx+=t[q].maxx;
        return p;
    }
    int mid=l+r>>1;
    t[p].ls=merge(t[p].ls,t[q].ls,l,mid);
    t[p].rs=merge(t[p].rs,t[q].rs,mid+1,r);
    push_up(p)
}

作用:

基本上都是处理 权值线段树 的各种情况,例如查询子树内前驱后缀,第 \(k\) 大, \(x\) 是第几大之类的权值线段树能干的各种事。

或者出现最多的数,总和最大的数.....

例题:

[POI2011]ROT-Tree Rotations

题意:

给定一棵二叉树,每个节点有权值为 \([1,n]\) 的排列,可以交换一些点的左右子树

求先序遍历后,形成的排列的逆序对数最小

分析:

先序遍历符合 \(中 \rightarrow 左 \leftarrow 右\) 的情况,因此先处理下面情况,再处理根节点是合理的。

因此,交换左右子树可以理解为:两棵线段树合并,根节点为其两棵树根节点的根节点

那么,怎么快速求子树的逆序对数?

线段树上存储数的大小,这让我们想起了 权值线段树

此时,交换的逆序对就是:

//合并线段树的过程中统计交换子树的逆序对个数L和不交换子树的逆序对个数R
int merge(int p,int q,int l,int r){
    if(!p) return q;
    if(!q) return p;
    if(l==r){
        t[p].sizes+=t[q].sizes;
        return p;
    }
    L+=1ll*t[t[p].rs].sizes*t[t[q].ls].sizes;
    R+=1ll*t[t[p].ls].sizes*t[t[q].rs].sizes;
    int mid=l+r>>1;
    t[p].ls=merge(t[p].ls,t[q].ls,l,mid);
    t[p].rs=merge(t[p].rs,t[q].rs,mid+1,r);
    t[p].sizes=t[t[p].ls].sizes+t[t[p].rs].sizes;
    return p;
}

然后求 \(min(L,R)\) ,统计总贡献,输出即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=5e5+5;

int n,cnt;
ll ans,L,R;
struct node{
    int ls,rs,sizes;
}t[N<<4];

int update(int l,int r,int val){
    int pos=++cnt;t[pos].sizes++;//开一个点的编号
    if(l==r) return pos;
    int mid=l+r>>1;
    if(val<=mid) t[pos].ls=update(l,mid,val);
    else         t[pos].rs=update(mid+1,r,val);
    return pos;
}
//合并线段树的过程中统计交换子树的逆序对个数L和不交换子树的逆序对个数R
int merge(int p,int q,int l,int r){
    if(!p) return q;
    if(!q) return p;
    if(l==r){
        t[p].sizes+=t[q].sizes;
        return p;
    }
    L+=1ll*t[t[p].rs].sizes*t[t[q].ls].sizes;
    R+=1ll*t[t[p].ls].sizes*t[t[q].rs].sizes;
    int mid=l+r>>1;
    t[p].ls=merge(t[p].ls,t[q].ls,l,mid);
    t[p].rs=merge(t[p].rs,t[q].rs,mid+1,r);
    t[p].sizes=t[t[p].ls].sizes+t[t[p].rs].sizes;
    return p;
}

int dfs(){
    int pos,val; scanf("%d",&val);
    if(val==0){
        int ls=dfs(),rs=dfs(); L=R=0;//找到线段树左右区间的编号
        pos=merge(ls,rs,1,n);//pos是合并后权值线段树的根节点
        ans+=min(L,R);
    }
    else pos=update(1,n,val);//向线段树中插值
    return pos;
}
int main(){
    cin>>n;
    dfs();
    cout<<ans<<endl;
    system("pause");
    return 0;
}

[HNOI2012]永无乡

题解:My Blog

posted @ 2021-10-27 12:01  Evitagen  阅读(130)  评论(0编辑  收藏  举报