Zju2112 Dynamic Rankings(树状数组套可持久化权值线段树)

这篇博客详细介绍了如何使用可持久化线段树和树状数组来解决一类动态区间查询问题,其中涉及序列的修改和查询第k小的数。文章首先阐述了问题背景,接着解释了不带修改的区间第k大数的求解方法,并分析了当序列发生修改时,如何高效地更新数据结构以应对新的查询。最后,博主给出了具体的算法实现,包括离散化、修改操作和查询操作的细节,并提供了一段C++代码作为示例。
摘要由CSDN通过智能技术生成

Zju2112 Dynamic Rankings

description

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1

],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改

变后的a继续回答上面的问题。

Input

第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。

分别表示序列的长度和指令的个数。

第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。

接下来的m行描述每条指令

每行的格式是下面两种格式中的一种。

Q i j k 或者 C i t

Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)

表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。

C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t

m,n≤10000

Output

对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

Sample Input

5 3 
3 2 1 4 7 
Q 1 4 3 
C 2 6 
Q 2 5 3 

Sample Output

3
6

solution

不带修的区间第 K K K大可持久化线段树利用 r o o t [ r ] − r o o t [ l − 1 ] root[r]-root[l-1] root[r]root[l1]版本的个数向前即可

本质是一个前缀和

考虑现在待修,显然就是修改后会对后面每个版本都造成影响,时间花费巨大

需要找一个很好的工具代替,这里我们就选择了 B I T \rm BIT BIT树状数组

每一个BIT节点表示一棵主席树,可持久化的是权值线段树

相同区间抽离出来就相当于一个对区间构建的树状数组

就在树状数组上查 [ l , r ] [l,r] [l,r]区间,里面相同区间的线段树相减就是值域属于 [ x , y ] [x,y] [x,y]的数的个数

树状数组是前缀和,主席树也是前缀和,所以可以套起来

树状数组里套主席树


具体而言:

将所有出现的值离散化(包括初始和修改)

对于位置 i i i的修改,相当于在树状数组上从 i i i跳到 n n n,在主席树的 a i a_i ai位置先减去,再在 k k k位置加一

对于区间 [ l , r ] [l,r] [l,r]的询问

l − 1 l-1 l1跳到 1 1 1的所有用到的树状数组的节点预处理到 L [ ] L[] L[]数组

r r r跳到 1 1 1的所有用到的树状数组的节点预处理到 R [ ] R[] R[]数组

然后在主席树上区间跳,统计对于区间 [ x , y ] [x,y] [x,y] L L L中的个数, R R R中的个数,相减就是 [ l , r ] [l,r] [l,r]区间中值域 [ x , y ] [x,y] [x,y]的个数

与此时的 k k k判断,线段树往左走还是往右走

这就相当于抽离了一个区间的树状数组出来

code

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 20005
struct query { int op, i, j, k; }q[maxn];
struct node { int tot, lson, rson; }t[maxn * 200];
int n, m, cnt, cnt_l, cnt_r;
int a[maxn], val[maxn], root[maxn], mp[maxn], L[maxn], R[maxn];

void modify( int &now, int lst, int l, int r, int id, int k ) {
	t[now = ++ cnt] = t[lst];
	t[now].tot += k;
	if( l == r ) return;
	int mid = ( l + r ) >> 1;
	if( id <= mid ) modify( t[now].lson, t[now].lson, l, mid, id, k );
	else modify( t[now].rson, t[now].rson, mid + 1, r, id, k );
}

int query( int l, int r, int k ) {
	if( l == r ) return l;
	int tot_l = 0, tot_r = 0;
	for( int i = 1;i <= cnt_l;i ++ ) tot_l += t[t[L[i]].lson].tot;
	for( int i = 1;i <= cnt_r;i ++ ) tot_r += t[t[R[i]].lson].tot;
	int mid = ( l + r ) >> 1;
	if( tot_r - tot_l >= k ) {
		for( int i = 1;i <= cnt_l;i ++ ) L[i] = t[L[i]].lson;
		for( int i = 1;i <= cnt_r;i ++ ) R[i] = t[R[i]].lson;
		return query( l, mid, k );
	}
	else {
		for( int i = 1;i <= cnt_l;i ++ ) L[i] = t[L[i]].rson;
		for( int i = 1;i <= cnt_r;i ++ ) R[i] = t[R[i]].rson;
		return query( mid + 1, r, k - ( tot_r - tot_l ) );
	}
}

int lowbit( int x ) { return x & ( -x ); }

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1;i <= n;i ++ ) scanf( "%d", &a[i] ), val[i] = a[i];
	for( int i = 1;i <= m;i ++ ) {
		char opt[5];
		scanf( "%s", opt );
		if( opt[0] == 'Q' ) 
			q[i].op = 0, scanf( "%d %d %d", &q[i].i, &q[i].j, &q[i].k );
		else 
			q[i].op = 1, scanf( "%d %d", &q[i].i, &q[i].k ), val[++ n] = q[i].k;
	}
	sort( val + 1, val + n + 1 );
	n = unique( val + 1, val + n + 1 ) - val - 1;
	for( int i = 1;i <= n;i ++ ) {
		a[i] = lower_bound( val + 1, val + n + 1, a[i] ) - val;
		for( int j = i;j <= n;j += lowbit( j ) ) 
			modify( root[j], root[j], 1, n, a[i], 1 );
	}
	for( int i = 1;i <= m;i ++ )
		if( ! q[i].op ) continue;
		else q[i].k = lower_bound( val + 1, val + n + 1, q[i].k ) - val;
	for( int i = 1;i <= n;i ++ ) mp[i] = val[i];
	for( int i = 1;i <= m;i ++ ) {
		if( q[i].op ) {
			for( int j = q[i].i;j <= n;j += lowbit( j ) )
				modify( root[j], root[j], 1, n, a[q[i].i], -1 );
			a[q[i].i] = q[i].k;
			for( int j = q[i].i;j <= n;j += lowbit( j ) )
				modify( root[j], root[j], 1, n, a[q[i].i], 1 );
		}
		else {
			cnt_l = cnt_r = 0; q[i].i --;
			for( int j = q[i].i;j;j -= lowbit( j ) ) L[++ cnt_l] = root[j];
			for( int j = q[i].j;j;j -= lowbit( j ) ) R[++ cnt_r] = root[j];
			printf( "%d\n", mp[query( 1, n, q[i].k )] );
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值