1. 程式人生 > >【題解】Luogu P5072 [Ynoi2015]盼君勿忘

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

眾所周知lxl是個毒瘤,Ynoi道道都是神仙題,題面好評

原題傳送門

一看這題沒有修改操作就知道這是莫隊題

我部落格裡對莫隊的簡單介紹

既然是莫隊,我們就要考慮每多一個數或少一個數對答案的貢獻是什麼

假設一個數x在區間[l,r]之間出現了y次,珂以很容易的求出該區間的長度length=r-l+1,那麼包含x的區間有\(2^{length-y}*(2^y-1)\),那麼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)\)

完整程式碼

// luogu-judger-enable-o2
#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;
}