【BZOJ3196】【TYVJ1730】二逼平衡树

本文介绍了一种利用树状数组与线段树相结合的方法来高效解决区间查询问题。通过将外层树状数组套用内层权值线段树的方式,实现了在O(LogNLogV)的时间复杂度内完成单点修改和区间查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目链接】

【思路要点】

  • 用外层树状数组套内层权值线段树,那么每一个区间对应的权值线段树可以表示为\(O(LogN)\)棵线段树的和(差)。
  • 那么在得到的线段树上二分即可在\(O(LogNLogV)\)的时间内解决题目中所给出的询问。
  • 注意修改仅涉及单点修改,那么至多会修改\(O(LogN)\)棵线段树,即修改的时间复杂度为\(O(LogNLogV)\)。
  • 总时间复杂度\(O(MLogNLogV)\),具体实现时需要对线段树进行动态开节点。

【代码】

#include<bits/stdc++.h>
using namespace std;
#define MAXN	50005
#define NODES	10000005
#define MAXV	100000005
struct Node {int lc,rc,sum;};
int n, m, size;
int value[MAXN], root[MAXN];
Node a[NODES];
int divide(int x, int y) {
	x += MAXV; y += MAXV;
	return (x + y) / 2 - MAXV;
}
int query(int root, int l, int r, int ql, int qr) {
	if (root == 0) return 0;
	if (l == ql && r == qr) return a[root].sum;
	int mid = divide(l, r), ans = 0;
	if (mid >= ql) ans += query(a[root].lc, l, mid, ql, min(qr, mid));
	if (mid + 1 <= qr) ans += query(a[root].rc, mid + 1, r, max(mid + 1, ql), qr);
	return ans;
}
int sum(int l, int r, int ql, int qr) {
	int ans = 0;
	for (int i = r; i >= 1; i -= i & -i)
		ans += query(root[i], -MAXV, MAXV, ql, qr);
	for (int i = l - 1; i >= 1; i -= i & -i)
		ans -= query(root[i], -MAXV, MAXV, ql, qr);
	return ans;
}
int find(int ql, int qr, int x) {
	static int v[MAXN], f[MAXN];
	int total = 0;
	for (int i = qr; i >= 1; i -= i & -i) {
		total++;
		v[total] = root[i];
		f[total] = 1;
	}
	for (int i = ql - 1; i >= 1; i -= i & -i) {
		total++;
		v[total] = root[i];
		f[total] = -1;
	}
	int l = -MAXV, r = MAXV;
	while (l < r) {
		int mid = divide(l, r), sum = 0;
		for (int i = 1; i <= total; i++)
			sum += f[i] * a[a[v[i]].lc].sum;
		if (x <= sum) {
			r = mid;
			for (int i = 1; i <= total; i++)
				v[i] = a[v[i]].lc;
		} else {
			x -= sum; l = mid + 1;
			for (int i = 1; i <= total; i++)
				v[i] = a[v[i]].rc;
		}
	}
	return l;
}
void maintain(int &root, int l, int r, int pos, int d) {
	if (root == 0) root = ++size;
	a[root].sum += d;
	if (l == r) return;
	int mid = divide(l, r);
	if (mid >= pos) maintain(a[root].lc, l, mid, pos, d);
	else maintain(a[root].rc, mid + 1, r, pos, d);
}
void maintain(int pos, int value, int d) {
	for (int i = pos; i <= n; i += i & -i)
		maintain(root[i], -MAXV, MAXV, value, d);
}
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &value[i]);
		maintain(i, value[i], 1);
	}
	for (int i = 1; i <= m; i++) {
		int opt, l, r, x, pos;
		scanf("%d", &opt);
		if (opt == 1) {
			scanf("%d%d%d", &l, &r, &x);
			printf("%d\n", sum(l, r, -MAXV, x - 1) + 1);
		}
		if (opt == 2) {
			scanf("%d%d%d", &l, &r, &x);
			printf("%d\n", find(l, r, x));
		}
		if (opt == 3) {
			scanf("%d%d", &pos, &x);
			maintain(pos, value[pos], -1);
			value[pos] = x;
			maintain(pos, value[pos], 1);
		}
		if (opt == 4) {
			scanf("%d%d%d", &l, &r, &x);
			printf("%d\n", find(l, r, sum(l, r, -MAXV, x - 1)));
		}
		if (opt == 5) {
			scanf("%d%d%d", &l, &r, &x);
			printf("%d\n",find(l, r, sum(l, r, -MAXV, x) + 1));
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值