题目

【JZOJ A组】【NOIP2017提高A组模拟7.10】区间_题解

思路

考虑分块
把n个序列分成长度为k的若干段,当然最后一段不一定是k。
然后对每一块求一个前缀、后缀乘积和。
最后暴枚区间,注意到每个区间刚好会被两段或一段覆盖,直接O(1)算即可。

代码
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=2e7+1;
int n,k,mod,b,c,d,ans;
int s[N],pre[N],suf[N];
int main()
{
    freopen("range.in","r",stdin); freopen("range.out","w",stdout);
    scanf("%d%d%d",&n,&k,&mod);
    scanf("%d%d%d%d",&s[1],&b,&c,&d);
    for(int i=2; i<=n; i++) s[i]=(s[i-1]*1ll*b+c)%d;
    int num=n/k;
    for(int i=0; i<num; i++)
    {
        int t=i*k+1;
        pre[t]=s[t];
        for(int j=t+1; j<t+k; j++) pre[j]=pre[j-1]*1ll*s[j]%mod;
        suf[t+k-1]=s[t+k-1];
        for(int j=t+k-2; j>=t; j--) suf[j]=suf[j+1]*1ll*s[j]%mod;
    }
    if(n%k)
    {
        int t=num*k+1;
        pre[t]=s[t];
        for(int i=t+1; i<=n; i++) pre[i]=pre[i-1]*1ll*s[i]%mod;
        suf[n]=s[n];
        for(int i=n-1; i>=t; i--) suf[i]=suf[i+1]*1ll*s[i]%mod;
    }
    for(int i=1; i<=n-k+1; i++)
        if(i%k==1) ans^=pre[i+k-1]; else
            ans^=suf[i]*1ll*pre[i+k-1]%mod;
    printf("%d",ans);
    return 0;
}