题目描述
给出一列数,试求在指定范围内有多少个不同的数. 数列的长度不大于 1000000 1000000 1000000,每个数的大小都不超过 1000000 1000000 1000000.
题目分析
我们不妨把问题简化,想一想只有一组询问的时候应该怎么做.
考虑最暴力的做法,
由数列从左到右,如果发现尚未标记的颜色,标记,颜色数加一.
我们发现,还有一种等价的做法,发现标记过的颜色后,颜色数先减一,取消该颜色的标记,然后再标记,颜色数加一.
可能有人会说这不就是没事找事干吗?实际上,以上两种操作方式是不一样的,后一种实际上告诉我们,不管一种颜色出现了多少次,数列的颜色数都只需要在最后一次出现时统计即可.
这对于我们做题有什么帮助呢?
我们不妨假设输入的查询中,查询的右界是依次递增的,那么很明显,我们可以由前一次查询的右界拓展到现在这一次查询的右界,那么我们就在拓展的过程中维护颜色数即可.
也就是说,最后一个位置前的该颜色完全没有意义,那么它完全可以被后面的该颜色替代.
得到这个结论后,我们考虑怎么使用它.
发现这个东西可以用树状数组来维护. 也就是说,我们维护树状数组,求区间时就统计区间前缀和.
每出现一个新的元素就 a d d ( 1 ) add(1) add(1),而取消前一个相同颜色对前缀和的影响,可以 a d d ( − 1 ) add(-1) add(−1).
最后区间前缀和作差,就是答案.
不要忘了之前的结论是在排序的情况下才有的!区间一定按右端点排序!
程序实现
#include<bits/stdc++.h>
#define maxn 1000010
using namespace std;
struct number{
int l,r,rank;
}num[maxn];
int n,m,tree[maxn],last_num[maxn],a[maxn],ans[maxn];
bool vis[maxn];
int lowbit(int x){return x&(-x);}
void add(int x,int k){
for(int i=x;i<=n;i+=lowbit(i))tree[i]+=k;
}
int query(int x){
int ret=0;
for(int i=x;i;i-=lowbit(i))ret+=tree[i];
return ret;
}
bool cmp(number x,number y){
return x.r <y.r ;
}//右端点小的在左边
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&num[i].l ,&num[i].r );
num[i].rank =i;
}
int next=1;
sort(num+1,num+m+1,cmp);
for(int i=1;i<=m;i++){
for(int j=next;j<=num[i].r ;j++){
add(j,1);
if(vis[a[j]])add(last_num[a[j]],-1); //这个颜色的前一个位置,要其对前缀和的消除影响
vis[a[j]]=true;
last_num[a[j]]=j;
}
next=num[i].r ;
ans[num[i].rank ]=query(num[i].r )-query(num[i].l -1);//前缀和作差
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);//别忘了按输入序输出
return 0;
}
题后总结
对于这种题目,还是不太能想到正解的方法.
想得到前缀和,但是想不到有关“最后一个才对整体有影响”之类的, a d d ( − 1 ) add(-1) add(−1)的操作也很妙.
很多题都有类似的贪心呢,关于影响的转移之类的.