经典模型:求区间不同数的个数
方法:主席树
其实思路与 树状数组 解决这题的思路是一模一样的。只不过主席树支持强制在线。
原理:对于一个右端点,不管我们的左端点询问在哪。对于同一个数多次出现的情况,我们实际上只关注离右端点最近的点。
在实现时,我们在新增的i处加入1个贡献。然后再删去上一个a[i]出现的位置的1个贡献.然后查询就是对一个历史版本数的一个简单的区间和查询
所以实际上这里其实不像 可持久化值域线段树 了.而是可持久化数组。我们用主席树维护的是 当右端点是 i i i 时,每个位置对答案的贡献。
我们就是利用主席树将每一个右端点的数组的状态都记录下来了。所以查询的时候直接查询历史版本即可。这也就是为什么主席树做这道题能够强制在线。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define mid ((l + r) >> 1)
const int maxn = 2e6 + 5;
template <typename T>
void read(T & x)
{
x = 0;T f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while (isdigit(ch)){
x = x * 10 + (ch ^ 48);
ch = getchar();
}
x *= f;
}
template <typename T>
void write(T x)
{
if(x < 0){
putchar('-');
x = -x;
}
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
int a[maxn];
int sum[maxn << 5] , ls[maxn << 5] , rs[maxn << 5] , rt[maxn] , tot;
int add (int l , int r , int t , int p , int c)
{
int now = ++tot;
ls[now] = ls[t];
rs[now] = rs[t];
sum[now] = sum[t] + c;
if (l == r) return now;
if (p <= mid) ls[now] = add (l , mid ,ls[now] , p , c);
else rs[now] = add (mid + 1 , r ,rs[now] , p , c);
return now;
}
int ask (int l , int r , int t , int L)
{
if (l == r) return sum[t];
int ans = 0;
if (L <= mid) ans = ask(l , mid , ls[t] , L) + sum[rs[t]];
else ans += ask(mid + 1 , r , rs[t] , L);
return ans;
}
int last[maxn];
int main()
{
int n; read(n);
int up = 1e6;
for (int i = 1 ; i <= n ; i++){
read(a[i]);
if (last[a[i]]) {
rt[i] = add(1 , up , rt[i - 1] , last[a[i]] , -1);
rt[i] = add(1 , up , rt[i] , i , 1);
}
else{
rt[i] = add(1 , up , rt[i - 1] , i , 1);
}
last[a[i]] = i;
}
int m; read(m);
while (m--){
int l , r;read(l);read(r);
write(ask(1 , up , rt[r] , l));puts("");
}
return 0;
}