Description
给出一个长度为n的序列,序列中的数再[1~c]的范围内,和m次询问,每次询问[l,r]这个区间中有多少个出现大于等于两次的数。
n,m,c<=10^6
Solution
看到这种题,有没有强制在线,自然很容易想到莫队~(≧▽≦)/~啦啦啦
可是n<=10^6,根号n的莫队表示很方。( ⊙ o ⊙ )啊!
于是,我们可以想到一个优美的替代品。
预处理出next[i]表示i后面第一个和i颜色相同的位置。
然后,把所有第二次出现的位置+1.
用树状数组维护。
把所有询问按左端点排序,处理完所有以i开始的讯问后,我们把next[i]的影响删除,把next[next[i]]加上。这样就能保证计算入答案的必然是这个区间里第二次出现的。
然后就没有了。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 1000005
using namespace std;
struct note{int l,r,id;}ask[N];
bool cmp(note x,note y){
return x.l<y.l;
}
int n,m,c,ans,a[N],next[N],f[N],t[N],an[N];
void add(int x,int y) {
for(;x<=n;x+=x&(-x)) t[x]+=y;
}
int find(int x) {
for(ans=0;x;x-=x&(-x)) ans+=t[x];
return ans;
}
int main() {
scanf("%d%d%d",&n,&c,&m);
fo(i,1,n) {
scanf("%d",&a[i]);
if (f[a[i]]) next[f[a[i]]]=i;
f[a[i]]=i;
}memset(f,0,sizeof(f));
fo(i,1,n) if (next[i]&&!f[a[i]]) f[a[i]]=1,add(next[i],1);
fo(i,1,m) scanf("%d%d",&ask[i].l,&ask[i].r),ask[i].id=i;
sort(ask+1,ask+m+1,cmp);int j=0;
fo(i,1,n) {
while (ask[j+1].l==i) an[ask[++j].id]=find(ask[j].r);
if (next[i]) add(next[i],-1);
if (next[next[i]]) add(next[next[i]],1);
}
fo(i,1,m) printf("%d\n",an[i]);
}