题目大意:
给出长度为 \(n\) 的序列,\(q\) 次询问,每次为区间 \([l, r]\) 有多少不同的数字。
思路:
显然可以用莫队解决,考虑带 log 的做法。
对于一个区间 \([l, r]\) 来说,区间内若有重复出现的数,那么他上一次出现的位置除了第一次出现的数以外,都在 \([l, r]\) 范围内。我们记录每个位置上的值上一次出现的位置为 \(last[i]\) ,问题转化为求 \([l, r]\) 上 \(last[i] < l\) 的个数。区间上的权值线段树,使用主席树解决。
Code:
struct Node { //每个点维护的是值域上值的个数
int lf, rt;
ll sum; //该节点的左节点为hjt[lf],右节点为hjt[rt],值为sum
} hjt[N * 40];
int tot = 0, root[N];
//pre的作用是now要依赖以上一个版本的权值线段树来建立
void insert(int l, int r, int pre, int &now, ll p) {
hjt[++tot] = hjt[pre]; //等于上一个版本线段树的当前节点
now = tot;
++hjt[now].sum;
if (l == r) return;
int m = (l + r) >> 1;
if (p <= m) insert(l, m, hjt[pre].lf, hjt[now].lf, p);
else insert(m + 1, r, hjt[pre].rt, hjt[now].rt, p);
}
//搜索到的当前节点所维护的区间为[l, r]
//我们当前要查询[L, R]的权值线段树,Lnow表示L - 1版本的权值线段树遍历到的当前节点,Rnow表示R版本的权值线段树遍历到的当前节点
ll query(int l, int r, int Lnow, int Rnow, int ql, int qr) {
if (ql <= l && r <= qr) {
return hjt[Rnow].sum - hjt[Lnow].sum;
}
int m = (l + r) >> 1;
ll ans = 0;
if (ql <= m)
ans += query(l, m, hjt[Lnow].lf, hjt[Rnow].lf, ql, qr);
if (qr > m)
ans += query(m + 1, r, hjt[Lnow].rt, hjt[Rnow].rt, ql, qr);
return ans;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int n, m;
cin >> n;
vector<int> a(n + 1);
vector<int> pre(N, 0);
vector<int> las(n + 1);
for (ll i = 1; i <= n; i++) {
cin >> a[i];
las[i] = pre[a[i]] + 1; //整体向右偏移一位
pre[a[i]] = i;
insert(1, n, root[i - 1], root[i], las[i]);
}
cin >> m;
for (ll i = 1, l, r; i <= m; i++) {
cin >> l >> r;
cout << query(1, n, root[l - 1], root[r], 1, l) << "\n";
}
return 0;
}