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