线段树合并
线段树合并
前置芝士:
动态开点线段树和权值线段树:
因为只有动态开点线段树,在合并时才不用完整建一棵线段树
问题:
如果有若干个线段树,维护相同的值域,并且在某一棵线段树上进行单点修改操作。
最后,我们希望把这些线段树对应位置上的值相加,同时维护区间最大值。
这样,就用到了线段树合并:
思想:
就是建立一棵新的线段树,保存原有的两棵线段树的信息。
方法:
用两个指针 \(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
不关注的有难了😠😠😠https://b23.tv/hoXKV9