P5072 [Ynoi2015] 盼君勿忘

题目

P5072 [Ynoi2015] 盼君勿忘

分析

莫队。

首先这道题有区间限制,数据范围也很明显,可以考虑离线莫队。

然后我们发现可以对于每一个数来算贡献,但是不好直接计算其出现次数,考虑转化。

发现其实可以转化成 \(2^{r-l+1}-2^{r-l-k+1}\) 次(全局的减掉不出现的,剩下的就是出现的),其中 \(k\) 是这个数的出现次数。

那么我们想一下怎么维护。

似乎每次直接暴力遍历所有出现过的数并不好,因为这样复杂度直接爆炸。

但是我们如果对每一个出现次数的所有数的和开一个数组,这样其实也有 \(O(n)\) 级别个数每次。

于是考虑平衡,因为这里涉及到了“划分”,也就是说,我们一共有 \(O(n)\) 级别个“单次出现”,现在这些元素被“划分”到了 \(n\) 个数里面去(就是把次数分配给了数)

那么如果我们根号平衡,发现就很好做了。

对于出现次数 \(\re\sqrt{n}\) 的数,这样的数不超过 \(\sqrt{n}\) 个,我们可以直接拿一个 \(unordered\)_\(map\) 来存这样的数(也可以链表维护)。

对于剩下的数,我们发现相当于其值域就在 \(\sqrt{n}\) 以内,我们可以直接拿数组 \(Cnt_i\) 来存:当前出现 \(i\) 次的所有数的和。

然后查询的时候直接遍历第一个的每一个数,和第二个的每一个出现次数即可。

然后注意这里的求幂次方不能直接快速幂,也不能固定模数,于是可以考虑 \(O(\sqrt{n})\) 单次预处理,\(O(1)\) 回答的光速幂。

代码

#include<bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x){
	x=0;bool f=false;char ch=getchar();
	while(!isdigit(ch)) f|=ch=='-',ch=getchar();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	x=f?-x:x;
	return ;
}
template <typename T>
inline void write(T x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10^48);
	return ;
}
const int N=1e5+5;
#define ll long long 
int n,m,a[N],bl[N],t;
struct Query{
	int l,r,mod,id;
	inline bool operator < (const Query &B)const{return (bl[l]^bl[B.l])?(l<B.l):((bl[l]&1)?r<B.r:r>B.r);}
	Query(int l=0,int r=0,int mod=0,int id=0):l(l),r(r),mod(mod),id(id){}
}Q[N];
unordered_set<int> S;
int cnt[N],Cnt[N];
ll Ans[N];
inline void Add(int x){
	cnt[x]++;
	if(cnt[x]==t) Cnt[cnt[x]-1]-=x,S.insert(x);
	if(cnt[x]<t) Cnt[cnt[x]-1]-=x,Cnt[cnt[x]]+=x; 
	return ;
}
inline void Del(int x){
	cnt[x]--;
	if(cnt[x]==t-1) Cnt[cnt[x]]+=x,S.erase(x);
	if(cnt[x]<t-1) Cnt[cnt[x]+1]-=x,Cnt[cnt[x]]+=x;
	return ; 
}
#define Inc(x,y,mod) (x+y>=mod?x+y-mod:x+y)
#define Dec(x,y,mod) (x-y<0?x-y+mod:x-y)
ll Pow[N],POW[N];
#define LogPow(x,mod) (POW[(int)floor((x)/t)]*Pow[x-(int)floor((x)/t)*t]%mod)
signed main(){
	read(n),read(m);t=sqrt(n)+1;
	const int block=n/sqrt(m);
	for(int i=1;i<=n;i++) read(a[i]),bl[i]=(i-1)/block+1;
	for(int i=1;i<=m;i++){
		int l,r,mod;
		read(l),read(r),read(mod);
		Q[i]=Query(l,r,mod,i);
	}
	sort(Q+1,Q+m+1);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(r<Q[i].r) Add(a[++r]);
		while(l>Q[i].l) Add(a[--l]);
		while(r>Q[i].r) Del(a[r--]);
		while(l<Q[i].l) Del(a[l++]);
		ll res=0;const ll mod=Q[i].mod;
		Pow[0]=1;POW[0]=1;
		for(int j=1;j<=t;j++) Pow[j]=Inc(Pow[j-1],Pow[j-1],mod);POW[1]=Pow[t];
		for(int j=2;j<=t;j++) POW[j]=POW[j-1]*POW[1]%mod;
		for(int j=1;j<t;j++) res=Inc(res,Dec(LogPow(r-l+1,mod),LogPow(r-l-j+1,mod),mod)*Cnt[j]%mod,mod);
		unordered_set<int>::iterator it=S.begin();
		for(;it!=S.end();it++) res=Inc(res,Dec(LogPow(r-l+1,mod),LogPow(r-l-cnt[*it]+1,mod),mod)*(*it)%mod,mod);
		Ans[Q[i].id]=res;
	}	
	for(int i=1;i<=m;i++) write(Ans[i]),putchar('\n');
	return 0;
}

最开始把题给看错了...以为必须是连续子序列...

光速幂的技巧值得学习,同时这里“全局”减掉“局部”的思想也非常巧妙。

posted @ 2021-05-07 00:19  __Anchor  阅读(41)  评论(0编辑  收藏  举报