看着莫隊的標籤去寫的 結果發現數據加強了 根本卡不過 後來想了想發現沒什麼好辦法 看題解區 發現了一個樹狀數組的寫法
確實很有意思
題意:給一個序列 對於每個詢問區間 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;
}