P4113 [HEOI2012]採花 莫隊(個屁)or 樹狀數組

看着莫隊的標籤去寫的 結果發現數據加強了 根本卡不過  後來想了想發現沒什麼好辦法  看題解區 發現了一個樹狀數組的寫法

確實很有意思

題意:給一個序列 對於每個詢問區間 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;
} 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章