hdu5286 wyh2000 and sequence

  题目大意:给一段长度为N的序列,每次询问l-r(l和r和上一次询问的答案有关)内 不同的数的 出现次数的次方 的和。

大体思路就是,把n个数分成sqrt(n)块,每块sqrt(n)个数,然后求出任意两块i到j(包含第i块和第j块)的ans,对于每次询问l和r,找到刚好包含l和r的一段连续的块,因为我们已经知道了这段连续块的答案,所以只要再去掉两段多出来的一部分,就好了。

为了得到任意两块间的答案G[i][j],我们可以枚举每个块的起点,然后for到最后,暴力一边,就好了,

对于多出来的一部分,我们只要知道这一部分里的数,在包含它的连续块中已经出现了几次,然后就可以很容易地更新答案了。于是我们可以先预处理出一个后缀和cnt[i][j]:表示从第i块开始,第j小的数出现了多少次,然后,对于每次询问q,我们 只要 只要 只要 (后缀和减一下)找到这一部分里的数在连续的块中出现了次数就好了,别的数就算了,都找的话,肯定超时。

之前还要预处理各种数次方的结果,还要排序,去重。

#include <bits/stdc++.h>
using namespace std;
const int MOD=1000000007;
const int maxn=5e4+5;
int cnt[240][maxn];//cnt[i][j]:从第i块开始到最后第j小的数出现的次数
long long A[maxn],a[maxn],num[maxn],vis[maxn];
int pos[maxn];//pos[i]:在A中下标为i的数是第几小
long long G[240][240];//G[i][j]:块i~j的答案
vector<long long>v[maxn];//v[i][j]:第i小的数的j次方
int n,m,unit,len,l,r;
int solve(int l,int r)
{
    int L=l/unit,R=r/unit;//获取所在的块
    long long ans=G[L][R];
    int LL=L*unit,RR=min(n-1,(R+1)*unit-1);//获取具体下标的的范围,且LL~RR肯定包含l~r的
    for(int i=LL;i<l;i++) num[pos[i]]=cnt[L][pos[i]]-cnt[R+1][pos[i]];
    for(int i=r+1;i<=RR;i++) num[pos[i]]=cnt[L][pos[i]]-cnt[R+1][pos[i]];
    while(LL<l)
    {
        ans-=v[pos[LL]][num[pos[LL]]];
        num[pos[LL]]--;
        ans+=v[pos[LL]][num[pos[LL]]];
        LL++;
    }
    while(RR>r)
    {
        ans-=v[pos[RR]][num[pos[RR]]];
        num[pos[RR]]--;
        ans+=v[pos[RR]][num[pos[RR]]];
        RR--;
    }
    return (ans%MOD+MOD)%MOD;
}
int main()
{
    int i,j,k,T;
    scanf("%d",&T);
    while(T--)
    {
        int la=0;
        scanf("%d%d",&n,&m);
        for(i=0;i<n;i++)
        {
            scanf("%lld",&A[i]);
            a[i]=A[i];
        }
        sort(a,a+n);
        len=unique(a,a+n)-a;
        for(i=0;i<len;i++) v[i].clear(),v[i].push_back(0),vis[i]=0;
        for(i=0;i<n;i++)
        {
            pos[i]=lower_bound(a,a+len,A[i])-a;//确定A[i]是第几小
            vis[pos[i]]++;
        }
        for(i=0;i<len;i++)
        {
            long long p=1;
            for(j=0;j<vis[i];j++)
            {
                p=p*a[i]%MOD;
                v[i].push_back(p);
            }
        }
        unit=sqrt(n);
        memset(cnt,0,sizeof(cnt));
        for(i=0;i*unit<n;i++)
        {
            long long ans=0;
            memset(num,0,sizeof(num));
            for(j=i*unit;j<n;j++)
            {
                cnt[i][pos[j]]++;
                ans-=v[pos[j]][num[pos[j]]];
                num[pos[j]]++;
                ans+=v[pos[j]][num[pos[j]]];
                if((j+1)%unit==0 || j==n-1)
                    G[i][j/unit]=(ans%MOD+MOD)%MOD;
            }
        }
        while(m--)
        {
            int L,R;
            scanf("%d%d",&L,&R);
            l=min((L^la)%n,(R^la)%n);
            r=max((L^la)%n,(R^la)%n);
            la=solve(l,r);
            printf("%d\n",la);
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值