ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

线段树之合并与分裂

2022-02-19 09:35:02  阅读:232  来源: 互联网

标签:重集 return rs int 线段 合并 mid 分裂 权值


请你维护一些可重集,初始只有一个编号为 \(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;
}

标签:重集,return,rs,int,线段,合并,mid,分裂,权值
来源: https://www.cnblogs.com/yukari1735/p/15911886.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有