题意:
给你一个长度为n的数组A(由题目给的产生式得出),然后有一个长度为m的窗口在数组A上,起始下标在[1,m]位置上
他会不断向右滑动,每一次向右移动一个单位长度。你需要计算的是每一次窗口覆盖的区间内所有数的乘积%P
最后输出的是这些乘积之和(注意这里不需要%P)
解析:
一开始用扩展欧几里得定理做,因为对于当前窗口[i,j]的答案是res,那么下一次窗口的答案res/A[i]*A[j]%P
但是这里面A[i]和P不一定互质,所以无法用到乘法逆元,网上搜了怎么求(a/c) mod p(c,p不互质)
看到有两种
1.扩展欧几里得
2.公式
用第一种方法T了,用第二种方法答案不对。
后来想了想,这里面A[i]都是mod p后的结果,所以代码里求的是 (a mod p) / (c mod p) mod p
但是我实际上要求的是 (a/c) mod p
用第一种方法,好像可以行.....但是T了....好像是题目n太大的原因(但是我想想好像原理又不太对.....)
第二种方法,它是换模数求的,所以最后求出来的是(a mod p) / (c mod p) mod p的结果,而不是 (a/c) mod p
即题目所求的是A[i+1...j] mod p,但是求出来的是(A[i...j] mod p ) / A[i] mod p的答案
然后以为是数学公式搜了题解...但是是一道思维题........
题解就是将n分块,分成块
然后对每一块独立的求前缀积和后缀积存在下标对应的数组种
然后当窗口刚好是完整的一块是,我们就用结尾元素的前缀积来做
如果不是,那么就肯定是起点在前一块,结尾在后一块,那么答案就是起始点的后缀积*结尾的前缀积%P
因为这道题对于一个滑动窗口,满足连续性ANS(A[i…j])=ANS(A[i….k])*ANS(A[k+1….j]) i<=k<j
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long int ll;
const int MAXN = 1e6+100;
int A[MAXN],P;
int pre[MAXN],bac[MAXN];
int main()
{
int n,m;
while(~scanf("%d%d%d",&n,&m,&P))
{
int x,y,z;
scanf("%d%d%d%d",&A[1],&x,&y,&z);
ll ans=1;
A[1]%=P;
int res=A[1];
ans=res;
for(int i=0;i<n/m+(n%m?1:0);i++)
{
for(int j=i*m+1;j<=i*m+m&&j<=n;j++)
{
if(j==1) continue;
A[j]=((1ll*x*res%P*res%P+1ll*y*res%P)%P+z%P)%P;
res=A[j];
if(!i) ans=ans*A[j]%P;
}
int tmp=1;
for(int j=i*m+1;j<=i*m+m&&j<=n;j++)
{
tmp=1ll*tmp*A[j]%P;
pre[j]=tmp;
}
tmp=1;
for(int j=min(i*m+m,n);j>=i*m+1;j--)
{
tmp=1ll*A[j]*tmp%P;
bac[j]=tmp;
}
}
for(int i=m+1;i<=n;i++)
{
if(i%m==0)
ans=ans+pre[i];
else
{
ans=ans+1ll*bac[i-m+1]*pre[i]%P;
}
}
printf("%lld\n",ans);
}
return 0;
}