HDU3333 Turing Tree (离线查询 + 线段树/树状数组 or 主席树)

18 篇文章 1 订阅
13 篇文章 1 订阅

题目链接: Turing Tree

大致题意

有一个长度为n的序列, 从1~n编号, 每个位置都有一个元素值.

询问: 多次查询, 每次查询[l, r]中去重后的元素总和.

解题思路

离线查询 + 线段树/ 树状数组 or 主席树

相似题目推荐

解法一: 主席树

主席树的思路其实比较好想. 这个题的问题本质是, 询问每段区间, 求不重复的元素的总和. 那么我们按照每一个位置, 都去建立一棵线段树, 树中维护[1, index]位置不重复元素之和.

当加到第index个位置时, 有两种情况: ①该数字在之前没有出现过 ②该数字在之前出现在pos位置过(pos < index). (关于位置pos的维护, 我们可以采用哈希表)

对于情况①, 我们正常统计即可. 情况②, 我们同样要更新当前点的情况, 但我们也要把前面点的贡献删除掉, 相当于在index位置+val, pos位置-val(val为当前位置的数值). 那么为什么我们要这样去做呢?

考虑到已经是第index版本的线段树, 我们需要处理的询问区间是[l, index], 很显然, 第pos位置出现的编号只能保证在[1, pos]区间产生贡献, 而[pos + 1, index]区间是无法产生贡献的, 因此会导致这个区间的贡献少了val. 而在index位置出现的编号一定能够保证在查询区间产生贡献. 因此index位置上的贡献是优于pos位置的, 又考虑到防止[1, pos]区间重复计算两次贡献, 所以要在pos位置-val, index位置+val.

这样我们查询[l, r]区间时, 我们在**第r棵线段树中直接查询[l, r]**即可. 考虑到优化空间, 于是采用主席树.


解法二: 离线查询 + 线段树(树状数组)

这个思路的做法核心是离线查询的思维, 线段树和树状数组只是为了维护区间贡献.

我们考虑将所有的查询存储下来, 按照右端点r进行小到大排序.
在处理询问时, 我们假设一个pos变量, 表示当前树中已经维护了前pos个位置的情况, 每次我们都让pos维护到当前查询的右边界r, 这样就保证了树中维护的区间是[1, r].

同主席树的思路, 当我们添加到第pos个位置时, 也是有两种情况. 处理思路也是相同的.

对于询问[l, r], 我们直接在树中查询[l, r]区间即可.

AC代码

/* 主席树 */
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 3E4 + 10;
struct node {
	int l, r;
	ll val;
}t[N << 5];
int root[N], ind;
int build(int a, int c, int tl, int tr, int p) {
	int x = ++ind; t[x] = t[p];
	t[x].val += c;
	if (tl == tr) return x;
	int mid = tl + tr >> 1;
	if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
	else t[x].r = build(a, c, mid + 1, tr, t[p].r);
	return x;
}
ll ask(int l, int r, int tl, int tr, int x) {
	if (l <= tl and r >= tr) return t[x].val;
	int mid = tl + tr >> 1;
	ll res = 0;
	if (l <= mid) res = ask(l, r, tl, mid, t[x].l);
	if (r > mid) res += ask(l, r, mid + 1, tr, t[x].r);
	return res;
}
int main()
{
	int T; cin >> T;
	while (T--) {
		ind = 0;

		unordered_map<int, int> mp;
		int n; scanf("%d", &n);
		rep(i, n) {
			int x; scanf("%d", &x);
			root[i] = build(i, x, 1, n, root[i - 1]);
			if (mp.count(x)) root[i] = build(mp[x], -x, 1, n, root[i]);
			mp[x] = i;
		}

		int m; scanf("%d", &m);
		rep(i, m) {
			int l, r; scanf("%d %d", &l, &r);
			printf("%lld\n", ask(l, r, 1, n, root[r]));
		}
	}
	return 0;
}



/* 离线查询 + 树状数组 */
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 3E4 + 10, M = 1E5 + 10;
int w[N];

struct operation {
	int l, r, id;
	bool operator< (const operation& t) const { return r < t.r; }
}; vector<operation> area;


ll t[N], res[M];
int lowbit(int x) { return x & -x; }
void add(int x, int c) { for (int i = x; i < N; i += lowbit(i)) t[i] += c; }
ll ask(int x) {
	ll res = 0;
	for (int i = x; i; i -= lowbit(i)) res += t[i];
	return res;
}
ll ask(int l, int r) { return ask(r) - ask(l - 1); }
int main()
{
	int T; cin >> T;
	while (T--) {
		area.clear(); memset(t, 0, sizeof t);

		int n; scanf("%d", &n);
		rep(i, n) scanf("%d", &w[i]);

		int m; scanf("%d", &m);
		rep(i, m) {
			int l, r; scanf("%d %d", &l, &r);
			area.push_back({ l, r, i });
		}
		sort(area.begin(), area.end());

		unordered_map<int, int> mp;
		int pos = 0;
		for (auto& op : area) {
			int l = op.l, r = op.r, id = op.id;
			while (pos + 1 <= r) {
				++pos;
				add(pos, w[pos]);
				if (mp.count(w[pos])) add(mp[w[pos]], -w[pos]);
				mp[w[pos]] = pos;
			}
			res[id] = ask(l, r);
		}

		rep(i, m) printf("%lld\n", res[i]);
	}
	return 0;
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值