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;
}
坑
最开始把题给看错了...以为必须是连续子序列...
光速幂的技巧值得学习,同时这里“全局”减掉“局部”的思想也非常巧妙。