首页 > 技术文章 > Luogu P5379 [THUPC2019]令人难以忘记的题目名称

cjoierShiina-Mashiro 2020-05-21 09:30 原文

Link
利用归纳法可以推出这样的一个结论:\(k\)轮之后胜利当且仅当\(S\)恰好差分\(k\)次之后变成全\(0\)序列。
我们知道差分\(t\)次之后\(\Delta^tS_i=\sum\limits_{k=0}^t(-1)^k{t\choose k}S_{i+k}\),因此\(\Delta^{p^k}S_i=S_i-S_{i+p^k}\)
那么我们先找到一个最小的\(t=p^k\),满足\(S_i=S_{i+t}\),这相当于\(S_i=S_{i+\gcd(t,n)}\)
\(p^a\|n\),若\(S\)\(p^a\)阶差分不是全\(0\)序列,那么一定无解,否则一定有解。
那么现在我们可以只保留\(S\)的前\(p^a\)项。
设答案为\(ans\),且\(ans\le p^{a-1}\),那么此时\(S\)一定是有一个循环节\(p^{a-1}\)
否则我们对\(S\)进行\(p^{a-1}\)阶差分直到\(S\)满足以\(p^{a-1}\)为循环节即可。
时间复杂度为\(O(np)\)

#include<cctype>
#include<cstdio>
#include<cstring>
const int N=300007;
int n,p,t,ans,a[N],b[N];
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
int check(int p)
{
    for(int i=0;i<n;++i) if(a[i]^a[(i+p)%n]) return 0;
    return 1;
}
int main()
{
    n=read(),p=read(),t=1;
    for(int i=0;i<n;++i) a[i]=read()%p;
    while(!(n%(t*p))) t*=p;
    if(!check(t)) return puts("-1"),0;
    for(n=t;n^1;n=t) for(t=n/p;!check(t);memcpy(a,b,4*n),ans+=t) for(int i=0;i<n;++i) b[i]=(a[i]-a[(i+t)%n]+p)%p;
    printf("%d",ans+!!a[0]);
}

推荐阅读