树套树 初步

树套树,顾名思义是将如 线段树、树状数组、平衡树 等树形数据结构结合起来,以实现更复杂的信息的维护

在线数据结构!!!

线段树/树状数组 套 动态开点线段树

通常用来处理二维信息

很常见的套路:数据结构题,有 l l l r r r 还有时间戳,对其中一维扫描线,另两维用树套树维护

注意,它最适合处理的是 单点修改、矩形查询 问题,因为外层线段树懒标记是不好传的,但有的题也能

时空复杂度均为 O ( n l o g 2 ) O(nlog^2) O(nlog2)

具体应用

1]  三维偏序

a i a_i ai 排序,另两维看作二维数点的话就是 单点加、矩形查 操作

静态线段树 套 值域线段树 版

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

typedef long long LL ;
const int N = 1e5+100 , M = 2e5+100 ; 

int n , K , f[N] ;
struct nn
{
	int a , b , c ;
}p[N] ;
bool cmp( nn x , nn y )
{
	return x.a < y.a ;
}
struct Segtree
{
	int l , r , rt ;
}t[8*N] ;
struct Segtree1
{
	int ls , rs , sum ;
}tre[240*N] ;// 内层动态开点线段树维护 C , log^2 个节点 
int tot ;
inline int tre_build() { return ++tot ; }
void build( int p , int l , int r )
{
	t[p].l = l , t[p].r = r , t[p].rt = tre_build() ;
	if( l == r ) {
		return ;
	}
	int mid = ( l + r ) >> 1 ;
	build( p<<1 , l , mid ) ; build( p<<1|1 , mid+1 , r ) ;
}
void Insert( int p , int l , int r , int x )
{
	if( l == r ) {
		tre[p].sum ++ ;
		return ;
	}
	int mid = ( l + r ) >> 1 ;
	if( x <= mid ) {
		if( !tre[p].ls ) tre[p].ls = tre_build() ;
		Insert( tre[p].ls , l , mid , x ) ;
	}
	else {
		if( !tre[p].rs ) tre[p].rs = tre_build() ;
		Insert( tre[p].rs , mid+1 , r , x ) ; 
	}
	tre[p].sum = tre[tre[p].ls].sum + tre[tre[p].rs].sum ;
}
void modify( int p , int B , int C )
{
	Insert( t[p].rt , 1 , K , C ) ; // 从上往下修改 
	if( t[p].l == t[p].r ) {
		return ;
	}
	int mid = ( t[p].l + t[p].r ) >> 1 ;
	if( B <= mid ) modify( p<<1 , B , C ) ;
	else modify( p<<1|1 , B , C ) ;
	// 无法 push up ,不能维护诸如 Max 等信息 
}
int tre_query( int p , int l , int r , int C )
{
	if( !p ) return 0 ;
	if( r <= C ) {
		return tre[p].sum ;
	}
	int mid = ( l + r ) >> 1 ;
	if( C <= mid ) return tre_query( tre[p].ls, l , mid , C ) ;
	return tre[tre[p].ls].sum + tre_query( tre[p].rs , mid+1 , r , C ) ;
}
int query( int p , int B , int C ) // 查 [1,B] 中的线段树 
{
	if( t[p].r <= B ) {
		return tre_query( t[p].rt , 1 , K , C ) ;
	}
	int mid = ( t[p].l + t[p].r ) >> 1 , res = 0 ;
	res += query( p<<1 , B , C ) ;
	if( mid < B ) res += query( p<<1|1 , B , C ) ;
	return res ;
}

int main()
{
	scanf("%d%d" , &n , &K ) ;
	for(int i = 1 ; i <= n ; i ++ ) {
		scanf("%d%d%d" , &p[i].a , &p[i].b , &p[i].c ) ;
	}
	sort( p+1 , p+n+1 , cmp ) ;
	build( 1 , 1 , K ) ; // 外层静态线段树维护 b 
	for(int i = 1 , j = 1 ; i <= n ; i ++ ) {
		while( j <= n && p[j].a == p[i].a ) {
			modify( 1 , p[j].b , p[j].c ) ;
			j ++ ;
		}
		int ans = query( 1 , p[i].b , p[i].c ) - 1 ;
		f[ans] ++ ;
	}
	for(int i = 0 ; i < n ; i ++ ) {
		printf("%d\n" , f[i] ) ; 
	}
	return 0 ;
}

我们会发现,外层线段树 无法进行 push up 操作!!!(也有可能是我还不会)

原因在于 没法在可观的复杂度内合并 lsrs 对应的线段树

因此我们只能采取递归时从上往下更新的方式

这样的后果是能够维护的信息大大受限制 (比如 Max 这样依赖于儿子节点的信息)

树状数组 套 值域线段树 版

struct BIT
{
	int rt[M] ;
	struct Segtree
	{
		int ls , rs , sum ;
	}t[16*16*M] ;
	int tot ;
	inline int build() { return ++tot ; }
	inline int lowbit( int x ) { return x&-x ; }
	void Insert( int p , int l , int r , int C )
	{
		if( l == r ) {
			t[p].sum ++ ;
			return ;
		}
		int mid = ( l + r ) >> 1 ;
		if( C <= mid ) {
			if( !t[p].ls ) t[p].ls = build() ;
			Insert( t[p].ls , l , mid , C ) ;
		}  
		else {
			if( !t[p].rs ) t[p].rs = build() ;
			Insert( t[p].rs , mid+1 , r , C ) ;
		}
		t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum ;
	 }
	void add( int p , int C )
	{
		for( ; p <= K ; p += lowbit(p) ) {
			Insert( rt[p] , 1 , K , C ) ;
		}
	}
	int query( int p , int l , int r , int C ) // <= C 
	{
		if( !p ) return 0 ;
		if( r <= C ) {
			return t[p].sum ;
		}
		int mid = ( l + r ) >> 1 , res = 0 ;
		res += query( t[p].ls , l , mid , C ) ;
		if( C > mid ) res += query( t[p].rs , mid+1 , r , C ) ;
		return res ;
	}
	int ask( int p , int C )
	{
		int res = 0 ;
		for( ; p ; p -= lowbit(p) ) {
			res += query( rt[p] , 1 , K , C ) ;
		}
		return res ;
	}
} T ;

树状数组本身就能将原序列任意一个区间 划分成 l o g log log

原来维护值的 t t t 数组改为维护线段树根节点的 r t rt rt 数组,查询\修改时在 t [ r t ] t[rt] t[rt] 中做

码量、常数 显著减小

2] 带修区间第 K K K传送门

树状数组 套 主席树

静态时我们主席树维护的时 是一段前缀信息,但这样带修根本做不了

考虑前缀信息用树状数组处理,我们就得到了 单修主席树

本题要求第 K K K 大,这种信息显然不具有可加减性,那么一个转化是找到某个数 x x x 使得小于它的元素恰有 K K K 个,这样对于元素个数的信息就能加减啦

直接二分是 O ( n l o g 3 ) O(nlog^3) O(nlog3) 的,我们照搬线段树二分的思路,对于树状数组上的 l o g log log 个区间让它们一起往儿子走,每层把 l o g log log 个节点拿出来就行

// 单修主席树 
struct BIT
{
	int rt[N] ;
	inline int lowbit( int x ) { return x&-x ; }
	struct Segtree
	{
		int ls , rs , sum ;
	}t[N*18*18] ;
	int tot ;
	inline int build() { return ++tot ; }
	void Insert( int p , int l , int r , int x , int f )
	{
		if( l == r ) {
			t[p].sum += f ;
			return ;
		} 
		int mid = ( l + r ) >> 1 ;
		if( x <= mid ) {
			if( !t[p].ls ) t[p].ls = build() ;
			Insert( t[p].ls , l , mid , x , f ) ;
		} 
		else {
			if( !t[p].rs ) t[p].rs = build() ;
			Insert( t[p].rs , mid+1 , r , x , f ) ;
		}
		t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum ;
	}
	void add( int p , int x , int f ) {
		for( ; p <= n ; p += lowbit(p) ) {
			Insert( rt[p] , 0 , INF , x , f ) ;
		}
	}
	int t1[32] , t2[32] , cnt1 , cnt2 , tp[32] , len ; // 同一层 log 个区间的,一起往下走 
	int query( int l , int r , int K )
	{
		if( l == r ) {
			return l ;
		} 
		int sum = 0 , mid = (l+r) >> 1 ;
		for(int i = 1 ; i <= cnt1 ; i ++ ) sum -= t[t[t1[i]].ls].sum ;
		for(int i = 1 ; i <= cnt2 ; i ++ ) sum += t[t[t2[i]].ls].sum ;
		if( sum >= K ) {
			len = 0 ;
			for(int i = 1 ; i <= cnt1 ; i ++ ) {
				if( t[t1[i]].ls ) tp[++len] = t[t1[i]].ls ;
			}
			for(int i = 1 ; i <= len ; i ++ ) t1[i] = tp[i] ;
			cnt1 = len ;
			len = 0 ;
			for(int i = 1 ; i <= cnt2 ; i ++ ) {
				if( t[t2[i]].ls ) tp[++len] = t[t2[i]].ls ;
			}
			for(int i = 1 ; i <= len ; i ++ ) t2[i] = tp[i] ;
			cnt2 = len ;
			return query( l , mid , K ) ;
		}
		K -= sum ;
		len = 0 ;
		for(int i = 1 ; i <= cnt1 ; i ++ ) {
			if( t[t1[i]].rs ) tp[++len] = t[t1[i]].rs ;
		}
		for(int i = 1 ; i <= len ; i ++ ) t1[i] = tp[i] ;
		cnt1 = len ;
		len = 0 ;
		for(int i = 1 ; i <= cnt2 ; i ++ ) {
			if( t[t2[i]].rs ) tp[++len] = t[t2[i]].rs ;
		}
		for(int i = 1 ; i <= len ; i ++ ) t2[i] = tp[i] ;
		cnt2 = len ;
		return query( mid+1 , r , K ) ;
	}
	int ask( int l , int r , int k ) {
		cnt1 = cnt2 = 0 ;
		l -- ;
		for( ; l ; l -= lowbit(l) ) t1[++cnt1] = rt[l] ;
		for( ; r ; r -= lowbit(r) ) t2[++cnt2] = rt[r] ;
		return query( 0 , INF , k ) ;
	}
	void pre_work()
	{
		for(int i = 1 ; i <= n ; i ++ ) {
			rt[i] = build() ;
		}
		for(int i = 1 ; i <= n ; i ++ ) add( i , a[i] , 1 ) ;
	}
} T ;

不知道为什么常数这么大 呜呜呜

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值