区间 分块的暴力做法

区间
Description
萌萌哒Salroey最近得到了一个长度为n的正整数数列Si,下标从1开始标号,她现在想让你对于一个给定正整数k,求出tj表示区间[j,j+k-1]中所有元素的乘积(1≤j≤n-k+1)。
为了方便输出,你只需要把所有tj对P取模之后,输出它们的异或和。
Input
第一行三个正整数n,k,P,分别表示序列长度,区间长度和模数。
第二行四个整数A,B,C,D,用来生成数据,Si定义如下:
S1=A;Si=(Si-1 × B + C) mod D
Output
输出一行一个整数表示ti mod P 的异或和
Sample Input
sample1:
4 2 10
5 1 1 10
sample2:
1000 97 96998351
41 1668 505 2333
Sample Output
sample1:
4
sample2:
1749769
Data Constraint
对于20%的数据,n≤1000
对于50%的数据,n≤2×10^5
另有20%的数据,n≤2×10^6;n-k≤10
对于100%的数据,1≤k≤n≤2×10^7;0≤A,B,C<D≤10^9;1≤P≤10^9
在所有数据中均匀分布着50%的数据满足P是质数,这50%的数据中有50%满足P≤10^7

看到这道题,我们很容易明白,题目的大概意思是,给出A,B,C,D,让我们从中推得长度为n的数列,并且,然后,对于每个长度为k的子序列,求出它的乘积并对P取模得到这个一个t,求出所有t 的异或和。
首先,我们知道,异或和的求取,跟各个异或的运算顺序并没有关系。
所以,只要把所有的长度为k的子序列求出来,并且互相异或便可得到答案。
但是,我们发现,D<10^9,也就是说,数列中的每个数的范围都小于10^9,而它们的乘积将大于int范围。但题目本身有空间限制 238592 kb,所以,我们无法开一个long long int的数组来存储一个所有的答案。
那么,我们可以使用前缀乘积来处理吗?
显然,我们发现,如果我们使用前缀乘积,那么对于第i个数,它的前缀乘积应该为 P*j+k(k<P),由于我们必须不断的模去P,所以我们最后得到的值是k,但是,对于一个区间[l,r],我们得到这个区间的乘积应该是 p[r]/p[l-1] ,在不断取模P的过程中,我们无法保证p[r]能一定整除p[l-1],并且,更不能确定是否出现了 p[r]/p[l-1]=P×a 但我们却得到 p[r]/p[l-1]=a 的情况。
而要解决这个问题,也可以使用高精度的方法,将多个数塞入一个位置,但这样的数组却不在题目限制范围内。
所以,我们可以选择以长度为k的区间进行分块,对于一个区间内部,准备两个数组front[i],behind[i],记录第i个点到这个区间第一个点的所有数的乘积,另一个记录下第i个点到这个区间末尾的所有数的乘积。
那么,对于一个区间[l,r],如果它被分块分成两个区间[l,i]、[j,r],那么,得到的答案就是behind[l]*front[r],并且,由于空间不足,我们使用int记录下数据,而在运算的过程中,使用(long long)强制转换,就可以保证运算不出错了。
当然,对于刚好[l,r]就是一块的情况,我们就直接让此时的ans^=behind[l]或者ans^=front[r]就可以得到答案了。

代码如下

#include <cstdio>
#include <iostream>
#include <algorithm>

#define LL long long int

using namespace std;

int n,k,p;
int b,c,d;
LL ans=0;
int x[20000002];
int h[20000001];


int main()
{
    scanf("%d%d%d",&n,&k,&p);
    scanf("%d",&b);
    h[1]=x[1]=b;
    scanf("%d%d%d",&b,&c,&d);
    for (int i=2;i<=n;i++)
    {
        x[i]=((LL)x[i-1]*(LL)b+c)%d;
        if (i%k==1)
            h[i]=x[i];
        else
            h[i]=((LL)h[i-1]*(LL)x[i])%p;
    }
    x[n+1]=1;
    for (int i=n;i>=1;i--)
    {
        if (i%k)
            x[i]=((LL)x[i+1]*(LL)x[i])%p;
    }
    for (int i=1;i<=(n-k+1);i++)
    {
        if (i%k==1)
            ans^=x[i];
        else
            ans^=((LL)x[i]*(LL)h[i+k-1])%p;
    }
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值