线段树之合并与分裂

请你维护一些可重集,初始只有一个编号为 \(1\) 的可重集 \(a\),要支持以下操作:

  1. 0 p x y:将可重集 \(p\) 中大于等于 \(x\) 且小于等于 \(y\) 的所有元素移动到一个新的可重集中。(其编号从 \(2\) 开始,是上次新建的可重集的编号 \(+1\)。)

  2. 1 p t:将可重集 \(t\) 中的元素全部移动到可重集 \(p\) 中,并清空可重集 \(t\)。(之后的操作中不会出现可重集 \(t\)。)

  3. 2 p x q:在可重集 \(p\) 中加入 \(x\) 个数 \(q\)。

  4. 3 p x y:询问可重集 \(p\) 中大于等于 \(x\) 且小于等于 \(y\) 的元素的个数。

  5. 4 p k:询问可重集 \(p\) 中第 \(k\) 小的元素,不存在则输出 -1

考虑用动态开点的权值树维护每个可重集,操作 2,3,4 是容易维护的。

接下来重点看一下 0,1 操作。

1 操作可以用一个线段树合并的方式解决。

一次合并操作会合并两个权值树,产生的权值树可以看作是两个原树对应位置相加而来的。

由于这些权值树维护的值域都是相同的,我们可以用两个指针同时遍历两颗权值树,相加对应节点的值。

设两个指针为 \(p,q\),要将 \(q\) 遍历的权值树合并到 \(p\) 遍历的权值树上。注意到动态开点的权值树有很多空节点,这时就需要分情况来讨论:

  1. 若 \(p,q\) 皆非空,在 \(p\) 处加上 \(q\) 处的值,删除 \(q\) 处节点,继续向下递归。

  2. 若 \(p\) 空,\(q\) 非空,则直接将以 \(q\) 为根的这颗子树接到 \(p\) 处,并返回。

  3. 若 \(p\) 非空,\(q\) 空,则直接返回。

  4. 若 \(p,q\) 皆空,直接返回。

代码就是这个样子的:

void merge( int & p , int & q , int l , int r ){
	if( ! p ){ p = q; return; }
	if( ! q ) return;
	if( l == r ){ val( p ) += val( q ) , delet( q ); return; }
	int mid = l + r >> 1;
	merge( ls( p ) , ls( q ) , l , mid );
	merge( rs( p ) , rs( q ) , mid + 1 , r );
	delet( q ) , push_up( p );
}

接下来考虑一下这个操作的复杂度。

注意到,若继续向下递归,必然会删除一个节点,于是总递归的次数不会超过总点数。注意这里的总递归次数值得并非一次操作,而是所有的合并操作。

而一次修改操作会新建 \(O(\log n)\) 个节点,\(m\) 次操作,总点数就是 \(O(m\log n)\) 的,所以所有合并操作的总复杂度就是 \(O(m\log n)\) 的。它的复杂度是均摊的。

分裂操作是提取出一颗权值树上的值域区间 \([x,y]\),对其新建一棵树,于是我们可以和区间操作一样,在原树上定位到对应的 \(\log\) 个区间,并将经过的节点提取出来构成一颗新树,并顺便在原树中将这个值域中的元素减掉。

其实还可以像 fhq-Treap 那个样子按排名分裂,不过上面这个方法更加简单,效率也高一些。

代码的实现:

void split( int & p , int & q , int l , int r , int L , int R ){
	if( ! p ) return;
	if( L <= l && r <= R ){ q = p , p = 0; return; }
	if( ! q ) q = newv();
	int mid = l + r >> 1;
	if( L <= mid ) split( ls( p ) , ls( q ) , l , mid , L , R );
	if( R > mid ) split( rs( p ) , rs( q ) , mid + 1 , r , L , R );
	push_up( p ) , push_up( q );
}

容易发现这个操作的复杂度和平凡的区间操作是一样的,为单次 \(O(\log n)\)。

其余三个操作的复杂度都是单次 \(O(\log n)\)。

总时间复杂度 \(O(m\log n)\)。

Code:

# include <cstdio>
# include <iostream>

namespace IO{
	inline int read(){
		int ret = 0 , ti = 1;
		char u = getchar();
		while( ! isdigit( u ) ){ if( u == '-' ) ti = -1; u = getchar(); }
		while( isdigit( u ) ) ret = ret * 10 + u - '0' , u = getchar();
		return ret * ti;
	}
}

using namespace std;

typedef long long ll;

const int N = 2e5 + 225;

int n , m;
int a[ N ];
int root[ N ] , nod , cnt;
int recy[ N << 2 ] , tp;

struct Node{ ll v; int lc , rc; }t[ N << 5 ];
# define ls( u ) t[ u ] . lc
# define rs( u ) t[ u ] . rc
# define val( u ) t[ u ] . v

inline int newv(){ return tp ? recy[ tp -- ] : ++ nod; }

inline void delet( int & u ){ ls( u ) = rs( u ) = val( u ) = 0 , recy[ ++ tp ] = u , u = 0; } 

inline void push_up( int u ){ val( u ) = val( ls( u ) ) + val( rs( u ) ); }

int build( int & u , int l , int r ){
	u = newv();
	if( l == r ){ val( u ) = a[ l ]; return u; }
	int mid = l + r >> 1;
	build( ls( u ) , l , mid );
	build( rs( u ) , mid + 1 , r );
	push_up( u );
	return u;
}

void split( int & p , int & q , int l , int r , int L , int R ){
	if( ! p ) return;
	if( L <= l && r <= R ){ q = p , p = 0; return; }
	if( ! q ) q = newv();
	int mid = l + r >> 1;
	if( L <= mid ) split( ls( p ) , ls( q ) , l , mid , L , R );
	if( R > mid ) split( rs( p ) , rs( q ) , mid + 1 , r , L , R );
	push_up( p ) , push_up( q );
}

void merge( int & p , int & q , int l , int r ){
	if( ! p ){ p = q; return; }
	if( ! q ) return;
	if( l == r ){ val( p ) += val( q ) , delet( q ); return; }
	int mid = l + r >> 1;
	merge( ls( p ) , ls( q ) , l , mid );
	merge( rs( p ) , rs( q ) , mid + 1 , r );
	delet( q ) , push_up( p );
}

void modify( int & u , int l , int r , int p , int x ){
	if( ! u ) u = newv();
	if( l == r ){ val( u ) += x; return; }
	int mid = l + r >> 1;
	if( p <= mid ) modify( ls( u ) , l , mid , p , x );
	else modify( rs( u ) , mid + 1 , r , p , x );
	push_up( u );
}

ll query1( int u , int l , int r , int L , int R ){
	if( ! u ) return 0;
	if( L <= l && r <= R ) return val( u );
	int mid = l + r >> 1;
	ll ret = 0;
	if( L <= mid ) ret += query1( ls( u ) , l , mid , L , R );
	if( R > mid ) ret += query1( rs( u ) , mid + 1 , r , L , R );
	return ret;
}

int kth( int u , int l , int r , int k ){
	if( l == r ) return l;
	int mid = l + r >> 1;
	if( val( ls( u ) ) >= k ) return kth( ls( u ) , l , mid , k );
	else return kth( rs( u ) , mid + 1 , r , k - val( ls( u ) ) );
}

void solve(){
	for( int i = 1 ; i <= m ; i ++ ){
		int op = IO :: read() , p = IO :: read();
		if( ! op ){
			int l = IO :: read() , r = IO :: read();
			split( root[ p ] , root[ ++ cnt ] , 1 , n , l , r );
		}
		else if( op == 1 ){
			int q = IO :: read();
			merge( root[ p ] , root[ q ] , 1 , n );
		}
		else if( op == 2 ){
			int c = IO :: read() , x = IO :: read();
			modify( root[ p ] , 1 , n , x , c );
		}
		else if( op == 3 ){
			int l = IO :: read() , r = IO :: read();
			printf( "%lld\n" , query1( root[ p ] , 1 , n , l , r ) );
		}
		else{
			int k = IO :: read();
			if( val( root[ p ] ) < k ) printf( "-1\n" );
			else printf( "%d\n" , kth( root[ p ] , 1 , n , k ) );
		}
	}
}

void input(){
	n = IO :: read() , m = IO :: read();
	for( int i = 1 ; i <= n ; i ++ ) a[ i ] = IO :: read();
}

int main(){
	input();
	build( root[ cnt = 1 ] , 1 , n );
	solve();
	system( "pause" );
	return 0;
}
上一篇:输入框内容换行转译


下一篇:【单片机学习】51单片机【串口】,详细介绍