牛客练习赛10 F-序列查询(莫队+链表)

F-序列查询

v5zsq题解

假设数字 x x x在区间[l,r]种出现y次,那么包含x的子区间个数为 2 r − l + 1 − y ⋅ ( 2 y − 1 ) 2^{r-l+1-y}·(2^y-1) 2rl+1y(2y1),因此对询问贡献是 x ⋅ 2 r − l + 1 − y ⋅ ( 2 y − 1 ) = x [ 2 r − l + 1 − 2 r − l + 1 − y ] x·2^{r-l+1-y}·(2^y-1)=x[2^{r-l+1}-2^{r-l+1-y}] x2rl+1y(2y1)=x[2rl+12rl+1y]
其中第一部分非常好维护第二部分的贡献,可以把出现次数相同的数一起维护贡献 sum[k]维护出现从次数为k的数字总和是多少。用个链表加快计算。

注意到一起区间中只有 O ( n ) O( \sqrt n ) O(n )种不同的出现次数因为 1 + 2 + . . . + n = O ( n ) 1+2+...+\sqrt n = O(n) 1+2+...+n =O(n)这是一个自然根号所以我们可以用一个均摊的莫队来维护区间可能的出现次数,从而维护区间中所有出现次数然后为了O(1)实现快速幂,我们可以每次 O ( n ) O(\sqrt n) O(n )算出 2 1 , 2 2 … 2 n m o d    p 2^1,2^2…2^{\sqrt n} \mod p 21,222n modp以及 2 n , 2 2 n … 2 n n m o d    p 2^{\sqrt n},2^{2\sqrt n}…2^{\sqrt n\sqrt n} \mod p 2n ,22n 2n n modp

#include<bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
ll qmi(ll a,ll b,ll mod)
{
    ll v=1;
    while(b)
    {
        if(b&1) v=v*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return v;
}
const int N=100005;
int Bs,b[N],a[N],n,m;
struct node
{
    int l,r,p,id;
    bool operator<(const node&o)const
    {
        if(b[l]==b[o.l])
        {
            if(b[l]&1) 
                return r<o.r;
            return r>o.r;
        }
        return b[l]<b[o.l];
    }
}q[N];
int num[N],cnt[N];
ll sum[N];
int h,fr[N],ne[N];
int ans[N];
int mod;
void insert(int x)
{
    ne[x]=h;
    fr[h]=x;
    fr[x]=0;
    h=x;
}
void del(int x)
{
    if(h==x) return h=ne[x],void();
    ne[fr[x]]=ne[x];
    fr[ne[x]]=fr[x];
}
void update(int x,int v)
{
    // 出现个数为num[x]-x
    if(num[x])
    {
        sum[num[x]]-=x;
        cnt[num[x]]--;
        if(!cnt[num[x]]) del(num[x]);
    }
    num[x]+=v;
    if(num[x])
    {
        sum[num[x]]+=x;
        cnt[num[x]]++;
        if(cnt[num[x]]==1) insert(num[x]);
    }
}
int add(int a,int b)
{
    a+=b;
    if(a>=mod) a-=mod;
    return a;
}
int mul(int a,int b)
{
    ll z=1ll*a*b;
    return z-z/mod*mod;
}
int f[1005],g[1005];
void init(int n)
{
    f[0]=1;
    for(int i=1;i<=Bs;i++) f[i]=add(f[i-1],f[i-1]);
    g[0]=1;
    for(int i=1;i<=n/Bs;i++) g[i]=mul(g[i-1],f[Bs]);
}
int Pow(int n)
{
    return mul(g[n/Bs],f[n%Bs]);
}
int query(int l,int r,int p)
{
    mod=p;
    int len=r-l+1;
    init(n);  
    int ans=0;
    for(int i=h;i;i=ne[i]) ans=add(ans,mul(sum[i]%p,add(Pow(len),p-Pow(len-i))));
    return ans;
}
int main()
{
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) a[i]=rd();
    Bs=sqrt(n)+1;
    for(int i=1;i<=n;i++) b[i]=(i-1)/Bs+1;
    for(int i=1;i<=m;i++) q[i].l=rd(),q[i].r=rd(),q[i].p=rd(),q[i].id=i;
    sort(q+1,q+1+m);
    
    int l=1,r=0;
    for(int i=1;i<=m;i++)
    {
        while(r<q[i].r) update(a[++r],1);
        while(r>q[i].r) update(a[r--],-1);
        while(l<q[i].l) update(a[l++],-1);
        while(l>q[i].l) update(a[--l],1);
        ans[q[i].id]=query(q[i].l,q[i].r,q[i].p);
    }
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值