【题解】Luogu P5072 [Ynoi2015]盼君勿忘

众所周知lxl是个毒瘤,Ynoi道道都是神仙题,题面好评

原题传送门

一看这题没有修改操作就知道这是莫队题

我博客里对莫队的简单介绍

既然是莫队,我们就要考虑每多一个数或少一个数对答案的贡献是什么

假设一个数x在区间[l,r]之间出现了y次,珂以很容易的求出该区间的长度length=r-l+1,那么包含x的区间有\(2^{length-y}*(2^y-1)\)\(2^{length-y}\)表示除了这x个相同的数,其他的数取与不取的情况数,\(2^y-1\)表示这x个数取与不取的情况数减掉一个不取的情况,那么x对答案的贡献为\(x*2^{length-y}*(2^y-1)\)\(x*2^{length}-x*2^{length-y}\)

从上式得知,x和y是相独立的,我们只需要统计出现次数为y的数字之和就行了

不同的y值最多有\(\sqrt n\)种,用一个线性列表维护这\(\sqrt n\)个的出现次数,维护每种出现次数的数字个数和总和,就能把查询的内容简化为一个长度为\(\sqrt n\)的多项式

由于每次的模数p不同,所以不能预处理2的幂,为了使求和时速度更快(毒瘤),我们把\(2^n\)变成\(2^{\lfloor \frac{n}{bl} \rfloor *bl +n \mod bl}\),其中\(bl=\lfloor \sqrt n \rfloor\)

每次只需要预处理\(2^0,2^1,...,2^{bl}\)\(2^{bl},2^{2*bl},...,2^{bl*bl}\)就珂以O(1)求出二的次幂(由于取模过慢,所以我程序里用了加减乘除代替取模)

转移是\(O(N \sqrt N)\)

查询也是\(O(N \sqrt N)\)

所以总复杂度为\(O(N \sqrt N)\)

完整代码

#include <bits/stdc++.h>
#define N 100005
#define ll long long
#define getchar nc
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf; 
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 
}
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
inline void write(register int x)
{
    if(!x)putchar('0');if(x<0)x=-x,putchar('-');
    static int sta[20];register int tot=0;
    while(x)sta[tot++]=x%10,x/=10;
    while(tot)putchar(sta[--tot]+48);
}
struct query{
    int l,r,p,id,bl;
}q[N];
int n,m,blocksize,a[N],ans[N];
inline bool cmp(register query a,register query b)
{
    return a.bl!=b.bl?a.l<b.l:((a.bl&1)?a.r<b.r:a.r>b.r);
}
int pre[N],nxt[N],head=0,vis[N],cnt[N];
ll sum[N];
inline void add(register int x)
{
    nxt[x]=head,pre[head]=x;
    head=x,pre[x]=0;
}
inline void del(register int x)
{
    if(x==head)
        head=nxt[x];
    else
        nxt[pre[x]]=nxt[x],pre[nxt[x]]=pre[x];
}
inline void change(register int x,register int type)
{
    if(cnt[a[x]])
    {
        sum[cnt[a[x]]]-=a[x];
        if(--vis[cnt[a[x]]]==0)
            del(cnt[a[x]]);
    }
    cnt[a[x]]+=type;
    if(cnt[a[x]])
    {
        sum[cnt[a[x]]]+=a[x];
        if(++vis[cnt[a[x]]]==1)
            add(cnt[a[x]]);
    }
}
int p,f[1000],g[1000];
inline int add(register int x,register int y)
{
    x+=y;
    if(x>=p)
        x-=p;
    return x;
}
inline int mul(register int x,register int y)
{
    return 1ll*x*y-1ll*x*y/p*p;
}
inline void init(register int n)
{
    f[0]=g[0]=1;
    for(register int i=1;i<=blocksize;++i)
        f[i]=add(f[i-1],f[i-1]);
    for(register int i=1;i<=n/blocksize;++i)
        g[i]=mul(g[i-1],f[blocksize]);
}
inline int Pow(register int n)
{
    return mul(g[n/blocksize],f[n%blocksize]);
}
inline int query(register int l,register int r,register int mod)
{
    p=mod;
    init(r-l+1);
    int ans=0;
    for(register int i=head;i;i=nxt[i])
        ans=add(ans,mul(sum[i]%p,add(Pow(r-l+1),p-Pow(r-l+1-i))));
    return ans;
}
int main()
{
    n=read(),m=read();
    blocksize=(int)sqrt(n);
    for(register int i=1;i<=n;++i)
        a[i]=read();
    for(register int i=1;i<=m;++i)
        q[i].l=read(),q[i].r=read(),q[i].p=read(),q[i].id=i,q[i].bl=(q[i].l-1)/blocksize+1;
    sort(q+1,q+1+m,cmp);
    int l=1,r=0;
    for(register int i=1;i<=m;++i)
    {
        while(l>q[i].l)
            change(--l,1);
        while(r<q[i].r)
            change(++r,1);
        while(l<q[i].l)
            change(l++,-1);
        while(r>q[i].r)
            change(r--,-1);
        ans[q[i].id]=query(q[i].l,q[i].r,q[i].p);
    }
    for(register int i=1;i<=m;++i)
        write(ans[i]),puts("");
    return 0;
}
posted @ 2018-12-08 21:49  JSOI爆零珂学家yzhang  阅读(547)  评论(2编辑  收藏  举报