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;
} 

 

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