【洛谷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;
}
posted @ 2019-11-12 17:26  TheLostWeak  阅读(...)  评论(... 编辑 收藏