【洛谷5072】[Ynoi2015] 盼君勿忘(莫队)
大致题意: 一个序列,每次询问一个区间\([l,r]\)并给出一个模数\(p\),求模\(p\)意义下区间\([l,r]\)内所有子序列去重后值的和。
题意转化
原来的题意看起来似乎很棘手,因此需要一定的转化。
考虑一个值\(x\)的贡献,设它在区间中出现的次数为\(cnt_x\),则共有\(2^{r-l+1}-2^{r-l+1-cnt_x}\)个子序列中有这个值,因此它的贡献就是\(x\cdot (2^{r-l+1}-2^{r-l+1-cnt_x})\)。
经这么一转化,不难发现,要求出这个式子的值,我们需要高效维护出区间内每个数的出现次数。
怎么维护呢?结合题目来源是Ynoi,我们不难想到莫队。
计算答案
通过莫队,我们能够在\(O(n\sqrt n)\)的时间内轻松求出\(cnt_x\)。
但必须要注意此题最大的坑点,因为模数是由询问给出的,所以我们无法直接维护答案中\(2\)的幂。
那么,我们就需要考虑,如何在维护\(cnt_x\)的基础上,对于每个询问,在\(O(\sqrt n)\)的时间内快速计算出答案。
研究\(x\cdot (2^{r-l+1}-2^{r-l+1-cnt_x})\)这个式子,可以发现在\(cnt\)相等时,后面\(2\)的幂是相同的,因此我们可以想到设\(sum_i=\sum_{cnt_x=i} x\),则答案就是\(\sum sum_i\cdot(2^{r-l+1}-2^{r-l+1-i})\)。
\(sum_i\)可以在维护\(cnt_x\)的时候一同维护,但如果我们求答案时真的去枚举\(i\),复杂度依然是\(O(n)\)。
所以我们可以考虑根号分治:
- 对于\(cnt_x\le\sqrt n\),我们直接枚举这\(\sqrt n\)个\(sum_i\)计算答案,单次询问复杂度为\(O(\sqrt n)\)。
- 对于\(cnt_x>\sqrt n\),由于\(\sum cnt_x=r-l+1\le n\),因此这样的\(x\)不超过\(\sqrt n\)个,可以在莫队同时用链表维护,然后询问时扫描链表计算答案,单次询问复杂度为\(O(\sqrt n)\)。
这样一来复杂度就做到了\(O(n\sqrt n)\)。
预处理\(2\)的幂——真正的\(O(n\sqrt n)\)
注意,如果用快速幂来求\(2\)的幂,显然,会让复杂度多带一个\(log\),这就让原本已经较高的复杂度更是难以接受,因此需要想办法去掉这个\(log\)。
怎样才能做到呢?很简单,其实预处理一下就可以了。
由于模数是询问中给出的,所以我们需要对于每个询问\(O(\sqrt n)\)预处理出\(2\)的幂。
实际上,我们只需预处理\(p2_i=2^i,p1_i=2^{i\sqrt n}\),然后就能实现\(O(\sqrt n)\)预处理、\(O(1)\)计算了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,Bs,Qt,a[N+5];
struct Query
{
int l,r,X,p,bl;I Query(CI x=0,CI y=0,CI g=0,CI i=0):l(x),r(y),X(g),p(i){Bs&&(bl=(x-1)/Bs+1);}
I bool operator < (Con Query& o) Con {return bl^o.bl?bl<o.bl:(bl&1?r<o.r:r>o.r);}
}q[N+5];
I int Qpow(RI x,RI y,RI X) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class MoQueueSolver
{
private:
#define P(x) (1LL*p1[(x)/Bs]*p2[(x)%Bs]%X)//计算2的幂
#define Ins(x) (nxt[x]=lnk,lst[lnk]=x,lnk=x)//链表中插入
#define Kill(x) ((x)^lnk?(lst[nxt[x]]=lst[x],nxt[lst[x]]=nxt[x]):(lnk=nxt[x]))//链表中删除
#define Add(x) cnt[x]==Bs&&Ins(x),cnt[x]<=Bs&&(sum[cnt[x]]-=x),++cnt[x]<=Bs&&(sum[cnt[x]]+=x)//加入元素
#define Del(x) cnt[x]==Bs+1&&Kill(x),cnt[x]<=Bs&&(sum[cnt[x]]-=x),--cnt[x]<=Bs&&(sum[cnt[x]]+=x)//删除元素
int X,ans[N+5],p1[N+5],p2[N+5],cnt[N+5],lnk,lst[N+5],nxt[N+5];long long sum[N+5];
I int Qry(CI l,CI r)//询问答案
{
RI i,p=P(r-l+1),t=0;for(i=1;i<=Bs;++i) t=(1LL*(p-P(r-l+1-i)+X)*(sum[i]%X)+t)%X;//对于小于等于sqrt(n)的部分
for(i=lnk;i;i=nxt[i]) t=(1LL*i*(p-P(r-l+1-cnt[i])+X)+t)%X;return t;//对于大于sqrt(n)的部分
}
public:
I void Solve()
{
RI i,j,L=1,R=0;for(sort(q+1,q+Qt+1),i=1;i<=Qt;++i)//莫队
{
W(R<q[i].r) ++R,Add(a[R]);W(L>q[i].l) --L,Add(a[L]);
W(R>q[i].r) Del(a[R]),--R;W(L<q[i].l) Del(a[L]),++L;
for(X=q[i].X,p1[0]=p2[0]=j=1;j<=Bs;++j) p2[j]=(p2[j-1]<<1)%X;//预处理2的幂
for(j=1;j<=(q[i].r-q[i].l+1)/Bs;++j) p1[j]=1LL*p1[j-1]*p2[Bs]%X;
ans[q[i].p]=Qry(q[i].l,q[i].r);//求解并记下答案
}
for(i=1;i<=Qt;++i) F.writeln(ans[i]);//输出答案
}
}M;
int main()
{
RI i,x,y,z;for(F.read(n),F.read(Qt),Bs=sqrt(n),i=1;i<=n;++i) F.read(a[i]);
for(i=1;i<=Qt;++i) F.read(x),F.read(y),F.read(z),q[i]=Query(x,y,z,i);//读入并存储询问
return M.Solve(),F.clear(),0;
}