P2617 Dynamic Rankings 动态主席树 (支持插入后再更新)

image
思路:
只看询问操作的话,就是很普通的主席树,但是这道题目里还增加了一个修改操作,这在以前的主席树题目中是没有的,即是是所谓的区间修改,那也是先处理好修改操作在进行查询,而不会在查询中夹着修改。
所以这道题我们需要另一种方法来解决。

假如我们在 x x x点处修改了 a x a_x ax的值,那么很明显在区间 [ x , n ] [x,n] [x,n]内的维护的数值都应该被更新,因为维护的相当于是一个前缀信息嘛,那么这时候我们要是再暴力的 [ x , n ] [x,n] [x,n]修改复杂度肯定不允许的,所以能想到用什么方法优化这个修改操作嘛。
这里联系主席树其实是维护的一种前缀性质的数量关系,类似的假如们不考虑这是一棵树,如果是一个普通的一位数组,我们在维护前缀和时,单点修改是怎么做的,树状数组/线段树。
那么考虑把一个单纯简易的点变成了一棵主席树后我们能否用同样的思路去实现呢,肯定是可以的,只需要树状数组维护的数组变成主席树的答案数组即可。就是 f o r ( . . . ) for(...) for(...)每次调用 m o d i f y modify modify单点更新主席树即可。
那么查询过程我们同理就类似于树状数组的查询过程操作就行,只不过每次我们想获得主席树某个点的数值的时候,同时有需要遵循主席树的查询过程,所以我们用两个数组维护一个当前主席树每层的节点对应的 r o o t root root号即可,每次就是两两做差维护。
这样就可以通过两两节点做差得到某个区间内的值。
然后就很顺利的可以求出每个区间对应的第 k k k大是多少了?

然后复杂度分析
时间复杂度很明显的就是 O ( n ∗ l o g n ∗ l o g n ) O(n*logn*logn) O(nlognlogn)的,空间复杂度的话我觉得是 O ( n ∗ l o g n ∗ l o g n ) O(n * logn * logn) O(nlognlogn)每次修改的时候会动 l o g n logn logn个点,而且 l o w b i t lowbit lowbit大约又是 l o g n logn logn次,所以我觉得是 O ( n ∗ l o g n + m ∗ l o g n ∗ l o g n ) O(n * logn + m * logn * logn) O(nlogn+mlognlogn),因为涉及到离散化操作,而后还有修改操作,所以需要我们离线处理询问,这里的 n n n代表的就是,离散化之后的值域范围。

#include <bits/stdc++.h>

using namespace std;

#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 1e5 + 10;
int n,m,a[N];
struct operation {//因为数据过大需要离散化 所以需要离线的存放一下询问的信息
	char op[10];
	int l,r,k,x,y;
}q[N];
int lsh[N * 4],cnt;
int getid(int x) { return lower_bound(lsh + 1,lsh + 1 + cnt,x) - lsh; }
//时间复杂度 O(n * logn * logn) 空间应该是

/******************带修主席树(树状数组 套 主席树)***********************/
int root[N * 2],tot;
struct ct {
	int l,r,sum;
}tree[N * 25 * 25];
void pushup(int now) { tree[now].sum = tree[tree[now].l].sum + tree[tree[now].r].sum; }
void build(int &now,int l,int r) {
	tree[now = ++tot]; tree[now].l = l,tree[now].r = r;
	if(l == r) { tree[now].sum = 0; return ; }
	int mid = (l + r) >> 1;
	build(tree[now].l,l,mid); build(tree[now].r,mid+1,r);
	pushup(now);
}
void modify(int &now,int pre,int l,int r,int p,int v) {
	tree[now = ++tot] = tree[pre];
	if(l == r) {
		tree[now].sum += v; return ;
	}
	int mid = (l + r) >> 1;
	if(p <= mid) modify(tree[now].l,tree[pre].l,l,mid,p,v);
	else modify(tree[now].r,tree[pre].r,mid+1,r,p,v);
	pushup(now);
}

int lowbit(int x) { return x & (-x); }
int totx,toty,x[N],y[N];
void qwork(int now,int pre) {//这是查询区间
	totx = toty = 0;
	for(int i = pre - 1;i;i -= lowbit(i)) x[++totx] = root[i];
	for(int i = now;i;i -= lowbit(i)) y[++toty] = root[i];
}

int query(int l,int r,int k) {//查询是过程中 就包含了 树状数组的lowbit思想
	if(l == r) return l;
	int mid = (l + r) >> 1;
	int ss = 0;//前缀和 区间求和嘛 前几个全减了去 其实就是在模拟树状数组求和的过程
	for(int i = 1;i <= totx;i ++) ss -= tree[tree[x[i]].l].sum;
	for(int i = 1;i <= toty;i ++) ss += tree[tree[y[i]].l].sum;
	if(k <= ss) {
		for(int i = 1;i <= totx;i ++) x[i] = tree[x[i]].l;
		for(int i = 1;i <= toty;i ++) y[i] = tree[y[i]].l;
		return query(l,mid,k);
	}
	else {
		for(int i = 1;i <= totx;i ++) x[i] = tree[x[i]].r;
		for(int i = 1;i <= toty;i ++) y[i] = tree[y[i]].r;
		return query(mid+1,r,k-ss);
	}
}
void add(int pos,int modifyx,int v) {//就是把普通的更新变成了 对树的logn更新
	for(int i = pos;i <= cnt;i += lowbit(i)) {
		modify(root[i],root[i],1,cnt,modifyx,v);
	}
}
/***********************************************************/

int main() {
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i ++) {
		scanf("%d",&a[i]);
		lsh[++cnt] = a[i];
	}
	for(int i = 1;i <= m;i ++) {
		scanf("%s",q[i].op);
		if(q[i].op[0] == 'Q') {
			scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].k);
		}
		else {
			scanf("%d%d",&q[i].x,&q[i].y);
			lsh[++cnt] = q[i].y;
		}
	}
	sort(lsh + 1,lsh + 1 + cnt);
	cnt = unique(lsh + 1,lsh + 1 + cnt) - lsh - 1;
	build(root[0],1,cnt);
	// cout << "--------\n";
	for(int i = 1;i <= n;i ++) {
		add(i,getid(a[i]),1);//其实也可以分两个数组 一个维护初始静态主席树 一个用bit的思想维护后续动态主席树
	}
	// cout << "-------\n";
	for(int i = 1;i <= m;i ++) {
		if(q[i].op[0] == 'C') {
			int p = q[i].x,v = q[i].y;
			// cout << q[i].x << ' ' << q[i].y << ' ' << getid(a[q[i].x]) << ' ' << getid(v) << '\n';
			add(p,getid(a[p]),-1);
			add(p,getid(v),1);
			// cout << "----\n";
			a[p] = v;
		}
		else {
			qwork(q[i].r,q[i].l);
			// cout << "-----\n";
			int ans = query(1,cnt,q[i].k);
			printf("%d\n",lsh[ans]);
		}
	}
	return 0;
}

/*
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值