题目链接: 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;
}