看着莫队的标签去写的 结果发现数据加强了 根本卡不过 后来想了想发现没什么好办法 看题解区 发现了一个树状数组的写法
确实很有意思
题意:给一个序列 对于每个询问区间 l~r 给出出现次数大于等于2的数的个数
我们知道树状数组离线可以解决区间有多少个不同数的问题(也就是出现次数大于等于1的个数) 那出现次数大于等于2怎么写呢
显然要知道一个数是不是出现两次 我们需要知道这个位置的数 下一次在哪个位置出现(下文都以后继表示) 这个可以预处理一下
我们把所有的询问按照左端点排序 一开始 对于每个数 出现第二次(仅仅是第二次)的位置在树状数组中+1
例如 序列 1 3 3 1 5 1 3那么对应的树状数组操作位置 0 0 1 1 0 0 0
然后我们从左到右对序列进行遍历 直到当前询问的左端点-1 如果我们遇到一个数 他有后继(也就是下一次出现的位置<n)那么我们把它下一次出现的位置-1 为什么呢 因为我们已经遍历过去这个位置了 这个位置对我们当前要遍历的询问没有贡献 那么这个位置下一次出现是不是肯定不足两次(对你肯定想到了其他情况 所以还有下一步) 然后如果这个数后继的后继存在的话 我们在其后继的后继处+1 这是显而易见的 因为此时这个数的后继和后继的后继都在我们的询问范围内 并且我们取消了后继的贡献 在后继的后继贡献上+1 保证贡献不会多出来 当我们遍历到当前询问的左端点后 就可以通过query操作得到答案了 手动模拟一下更清晰
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+100;
int val[N],lans[N],d[N],n,m,c,nex[N],nnex[N],mp[N],cnt[N];
inline int in(){
int w=0,x=0;char c=0;
while(c>'9'||c<'0') w|=c=='-',c=getchar();
while(c<='9'&&c>='0') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return w?-x:x;
}
void add(int x,int val){
while(x<=n){
d[x]+=val;
x+=x&-x;
}
}
int query(int x){
int ret = 0;
while(x){
ret+=d[x];
x-=x&-x;
}
return ret;
}
struct node{
int l,r,id;
bool operator < (const node &a){
return l<a.l;
}
}q[N];
int main(){
n=in(),c=in(),m=in();
for(int i = 1; i <= n; i++)
val[i]=in();
for(int i = n; i >= 1; i--){
if(mp[val[i]]) nex[i]=mp[val[i]];//后继
mp[val[i]]=i;
}
for(int i = 1; i <= n; i++){
cnt[val[i]]++;
if(cnt[val[i]]==2) add(i,1);//仅在第二次出现+1
nnex[i]=nex[nex[i]];//后继的后继
}
for(int i = 1; i <= m; i++)
q[i].l=in(),q[i].r=in(),q[i].id=i;
sort(q+1,q+1+m);
int t = 1;
for(int i = 1; i <= m; i++){
while(t<q[i].l){
if(nex[t]) add(nex[t],-1);
if(nnex[t]) add(nnex[t],1);
t++;
}
//printf("i=%d l=%d r=%d\n",i,q[i].l,q[i].r);
lans[q[i].id]=query(q[i].r)-query(q[i].l-1);
}
for(int i = 1; i <= m; i++) printf("%d\n",lans[i]);
return 0;
}