ZOJ2112——Dynamic Rankings(动态第k小)

题意:

n个数,q个询问 (n<=50000, q<=10000)

Q x y z 代表询问[x, y]区间里的第z小的数

C x y 代表将(从左往右数)第x个数变成y

思路:

树状数组套主席树

先建立一颗静态的主席树,将初始值依次插入并建树。所谓静态指:可以查询,不在这颗树上进行修改操作。

对于修改操作,另外建一批主席树,每个树管理一个位置(即1-n)的权值信息,1-n位置的权值初始都为0,因此可以把这批主席树的根都初始化为root[0]。

对于静态主席树来说,建树过程中,Tree[i]是在Tree[i-1]的基础上继承并发展而来的;而第二批主席树则不然,它每棵树只保存一个位置的权值信息,i位置的权值并不叠加i-1位置的权值,他们是独立的,所以当需要区间统计的时候,就需要我们手工求和,区间求和,自然想到树状数组。

根据树状数组,当要更新i位置时,也更新i+lowbit(i)…到n,当要查询1-i的前缀和就查i, i-lowbit(i)…到1即可。

对于这两批主席树,可以做一个比喻帮助理解:假设我现在给你一个数,要求在多次加或减运算后求出结果,可以这样做就(当然不用这么麻烦,只是配合举例):开一个变量x把给的那个数作为初始值先存下来,再来一个变量y记录每次的+或-值,记录变化,最后只需x+y就算出来了。这里的x就相当于第一批静态主席树,y就相当于第二批记录修改的主席树。

还不懂可以看看:传送,不过我觉得他后面有的图画的有点迷。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 60000+50;
const int maxq = 10000+50;
int n, m;
struct query{
	int a,b,c;
}Q[maxq];

int lson[maxn<<5], rson[maxn<<5], Tree[maxn<<5], root[maxn];
int S[maxn];
int use[maxn];	// use[x]: 记录在x位置的线段树现在该从哪个节点了向下查了,层层记录,逐步下移
int A[maxn], Hash[maxn];	// 原数组和哈希数组 

int cnt;
void Build(int &rt, int l, int r){
	rt = ++cnt; Tree[rt] = 0;
	if(l == r) return;
	int mid = (l+r) >> 1;
	Build(lson[rt], l, mid);
	Build(rson[rt], mid+1, r);
}

void update(int& new_rt, int old_rt, int l, int r, int pos, int val){
	new_rt = ++cnt;
	lson[new_rt] = lson[old_rt]; rson[new_rt] = rson[old_rt];
	Tree[new_rt] = Tree[old_rt] + val;
	if(l == r) return;
	int mid = (l+r) >> 1;
	if(pos <= mid) update(lson[new_rt], lson[old_rt], l, mid, pos, val);
	else update(rson[new_rt], rson[old_rt], mid+1, r, pos, val);
}

inline int lowbit(int x){return x & (-x);}
int sum(int x){
	int ans = 0;
	while(x > 0){
		ans += Tree[lson[use[x]]];
		x -= lowbit(x);
	}
	return ans;
}

int Query(int la, int rb, int rta, int rtb, int l, int r, int k){
	if(l == r) return l;
	int mid = (l+r) >> 1;
	int leftot = Tree[lson[rtb]] - Tree[lson[rta]] + sum(rb) - sum(la);		// 静态主席树+树状数组 统计出左边一共有多少数 
	if(k <= leftot){	// kth 在左边
		for(int i = la; i > 0; i-=lowbit(i)) use[i] = lson[use[i]];
		for(int i = rb; i > 0; i-=lowbit(i)) use[i] = lson[use[i]];
		return Query(la, rb, lson[rta], lson[rtb], l, mid, k);
	}
	else{
		for(int i = la; i > 0; i-=lowbit(i)) use[i] = rson[use[i]];
		for(int i = rb; i > 0; i-=lowbit(i)) use[i] = rson[use[i]];
		return Query(la, rb, rson[rta], rson[rtb], mid+1, r, k-leftot);
	}
}

void add(int x, int pos, int val){
	while(x <= n){
		update(S[x], S[x], 1, m, pos, val);
		x += lowbit(x);
	}
}


int main()
{
	//freopen("E:/Cgit/Practice/ACM/in.txt","r",stdin);
	int T, q; scanf("%d",&T);
	char ch[5]; 
	while(T--){
		scanf("%d%d",&n,&q);
		m = 0;
		for(int i = 1; i <= n; ++i) scanf("%d", &A[i]), Hash[++m] = A[i];
		
		for(int i = 1; i <= q; ++i){
			scanf("%s", ch);
			scanf("%d%d", &Q[i].a, &Q[i].b);
			if(ch[0] == 'C'){
				Hash[++m] = Q[i].b;
				Q[i].c = -1;
			}
			else{
				scanf("%d", &Q[i].c);
			}
		}
		// 离散化 
		sort(Hash+1, Hash+m+1);
		int tmp = 1;
		for(int i = 2; i <= m; ++i) if(Hash[i] != Hash[i-1]) Hash[++tmp] = Hash[i];
		m = tmp;
		//printf("m = %d\n", m);
		// 建静态主席树 
		cnt = 0;
		Build(root[0], 1, m);
		
		// 插入初始值 
		for(int i = 1; i <= n; ++i){
			int pos = lower_bound(Hash+1, Hash+m+1, A[i]) - Hash;
			update(root[i], root[i-1], 1, m, pos, 1);
		}
		//for(int i = 1; i <= 30; ++i) printf("%d ", root[i]);
		// 另外建n颗主席树 
		for(int i = 1; i <= n; ++i) S[i] = root[0];		// 初始化 
		for(int i = 1; i <= q; ++i){
			int a = Q[i].a, b = Q[i].b, c = Q[i].c; 
			if(c == -1){	// change a b
				int pos1 = lower_bound(Hash+1, Hash+m+1, A[a]) - Hash;
				int pos2 = lower_bound(Hash+1, Hash+m+1, b) - Hash;
				add(a, pos1, -1);
				add(a, pos2, 1);
				A[a] = b;
			}
			else{	// query a b c 
				for(int j = a-1; j > 0; j-= lowbit(j)) 
					use[j] = S[j];
				for(int j = b; j > 0; j-= lowbit(j)) 
					use[j] = S[j];
				int pos = Query(a-1, b, root[a-1], root[b], 1, m, c);
				printf("%d\n", Hash[pos]);
			} 
		}
	}
	fclose(stdin);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值