题解 agc052c 【Nondivisible Prefix Sums】
到处翻题解总算把这道题全部搞懂了,发现网上资源质量参差不齐,大多因为各种原因导致阅读起来有障碍,就总结了两篇篇题解结合自己理解写了这篇题解,并且对于两篇题解里的一些部分进行了修订和完善(若有错误,欢迎指正;若有更简洁的方法,欢迎留言和私信;若有侵权,请立即与我联系).
首先,让我们谈论一下数组什么时候是好的.
如果一个数组所有元素的总和都能被\(p\)整除,那显然是不好的.从现在开始,我们假设所有元素的和不能被\(p\)整除.
设\(X\)为出现次数最多的元素(或其中之一),假设它在数组中出现\(M\)次.将所有的元素乘以\(X^{-1}\),这样现在\(1\)是最常见的元素.
设\(B_1,B_2,\cdots,B_K\)是数组中大于\(1\)的所有元素.
引理:当且仅当\(M\le(p-B_1)+(p-B_2)+\cdots+(p-B_K)+p-1\)时,可以重新排序后满足条件,即原数组是好的.
必要性证明:
假设\(M\)大于这个数,那么\(M\)至少是\((p-B_1)+(p-B_2)+\cdots+(p-B_K)+p+1\)(因为总和不能被\(p\)整除).所以,所有数的总和至少是\(p(K+1)+1\).
我们必须"跳"过点\(p,2p,\cdots,(K+1)p\),对于每一个这样的"跳",我们需要一个大于\(1\)的元素.然而,我们只有\(K\)个这样的元素,并且一个元素最多可以"跳"一次,所以这样的无论如何重新排序都是不可能的.
充分性证明:
我们构造一种排序:
设\(x\)是当前最常见的元素之一,\(cur\)是当前的和.
-
如果\(cur+x\)不能被\(p\)整除,则在末尾附加\(x\).
-
否则取任何不是\(x\)的元素,比如\(y\),然后附加\(y,x\).
在第二种情况下,前缀和不会能被\(p\)整除:如\(cur+x\)可以被\(p\)整除,那么\(cur+y\)和\(cur+y+x\)就不能被\(p\)整除.
这个算法什么时候会失败?只有\(cur+x\)可被\(p\)整除,并且除\(x\)之外没有其他元素时.
这意味着我们至少还有\(2\)个元素\(x\)(否则总和将被\(p\)整除).所以,我们有\(\ge2\)个\(x\),没有其他元素.
这意味着\(x\)总是剩下的元素中出现次数最多的元素(我们可以证明,如果某个元素\(y\)在某个操作时上是剩下元素中最出现次数最多的,那么在将来的操作时都不可能其出现次数大于出现次数最多的元素的出现次数\(+1\)).
所以,\(x=1\),我们总是在可能的时候取\(1\).这意味着我们的序列看起来像这样:
\(p-1\)个\(1\),\(B_1\),\(p-B_1\)个1,\(B_2\),\(p-B_2\)个\(1\),\(\cdots\),\(B_K\),\(p-B_K\)个\(1\),现在我们不能进行操作,所以我们至少还有\(>1\)个\(1\).
但是,那么\(1\)的数量就会超过条件,矛盾.
总的数组数为\((p-1)^N\).
考虑容斥,我们将计算不是好数组的数量.
设所有元素的和表示为\(\sum a_i\).
先求满足\(p\nmid\sum a_i\)的数组的数量.
考虑这样一个过程:
当\(N=1\)时,所有\(\sum a_i\equiv1,2,3,\cdots,p-1(\mod p)\)分别有\(1\)种数组,为\(0\)有\(0\)种.
假设当\(N=x\)时,\(\sum a_i\equiv1,2,3,\cdots,p-1(\mod p)\)分别有\(y\)种数组,\(\equiv0\)有\(y-1\)种.那么当\(N=x+1\)时,\(\sum a_i\equiv1,2,3,\cdots,p-1(\mod p)\)分别有\((p-1)y-1\)种数组,\(\equiv0\)有\((p-1)y\)种.
同理假设当\(N=x\)时,\(\sum a_i\equiv1,2,3,\cdots,p-1(\mod p)\)分别有\(y-1\)种数组,为\(0\)有\(y\)种.那么当\(N=x+1\)时,\(\sum a_i\equiv1,2,3,\cdots,p-1(\mod p)\)分别有\((p-1)y\)种数组,\(\equiv0\)有\((p-1)y-1\)种.
所以当\(N\)为奇数时,满足\(p\nmid\sum a_i\)的数组的数量为\(\frac{(p-1)^n-(p-1)}{p}\),当\(N\)为偶数时\(\frac{(p-1)^n+(p-1)}{p}\).
在考虑从所有满足\(p\nmid\sum a_i\)的数组中减去不符合上述引理的数组数.
考虑决策所有大于\(1\)的\(a_i\)构成的数列,然后把\(1\)插入到数列中,可以DP,记\(f(cnt,sum)\)表示当前由大于\(1\)的\(a_i\)构成的数列长度为\(cnt\),\(\sum p-a_i\)为\(sum\)的方案数.
\(f(i,j)=\sum_{k=j-min(P-2,j)}^{j-1}f(i-1,j-k)\)
那么不符合结论的数组数量为:
\(\sum_{cnt}\sum_{sum}[n-cnt\not\equiv sum][n-cnt\ge p+sum]f(cnt,sum)\binom{n}{cnt}(p-1)\)
组合数\(\binom{n}{cnt}\)即插入\(1\)的方案数,再乘上\(p-1\)是因为我们默认了出现次数最多的是\(1\),实际上\(1\sim p-1\)都可以.
考虑DP的两维状态的取值范围,显然\(cnt\)范围为\([0,n]\).而\(sum\)的取值范围需要分析:注意到\(f(cnt,sum)\)对答案有贡献需要满足\(n-cnt\ge p+sum\),那么\(sum\)也不能超过\(n\).
所以只需进行一个状态数是\(\mathcal{O}(n^2)\)的DP,经过前缀和优化可以\(\mathcal{O}(1)\)转移.
状态数可以用滚动数组优化.
总时间复杂度\(\mathcal{O}(n^2)\),空间复杂度\(\mathcal{O}(n)\).
下面贴一下自己写的代码.
#include<algorithm>
#include<iostream>
#include<complex>
#include<cstdlib>
#include<cstring>
#include<utility>
#include<bitset>
#include<cstdio>
#include<string>
#include<time.h>
#include<vector>
#include<cmath>
#include<deque>
#include<queue>
#include<map>
#include<set>
using namespace std;
const int N=5e3+5,M=998244353;
int d[N],c[N],l[N],s[N];
int ksm(int a,int b)
{
int an=1;
while(b)
{
if(b&1)
an=1ll*an*a%M;
a=1ll*a*a%M;
b>>=1;
}
return an;
}
int zh(int x,int y)
{
return 1ll*c[x]*l[y]%M*l[x-y]%M;
}
int main()
{
int n,p,a,h;
scanf("%d%d",&n,&p);
if(n&1)
a=1ll*(ksm(p-1,n-1)-1)*ksm(p,M-2)%M;
else
a=1ll*(ksm(p-1,n-1)+1)*ksm(p,M-2)%M;
c[0]=1;
for(int i=0;i<n;i++)
c[i+1]=1ll*c[i]*(i+1)%M;
l[n]=ksm(c[n],M-2);
for(int i=n-1;~i;i--)
l[i]=1ll*l[i+1]*(i+1)%M;
for(int i=1;i<n-p;i++)
s[i]=1;
if(n%p&&p<=n)
a++;
for(int i=1;i<=n;i++)
{
h=0;
for(int j=i;j<=n-i-p;j++)
{
d[j]=(s[j]-s[j-min(p-2,j)])%M;
if((n-i-j)%p)
h=(h+d[j])%M;
}
a=(a+1ll*h*zh(n,i))%M;
memset(s,0,sizeof(s));
for(int j=i;j<n-i-p;j++)
s[j+1]=d[j];
for(int j=i+1;j<n-i;j++)
s[j+1]=(s[j+1]+s[j])%M;
}
printf("%lld",((ksm(p-1,n)-1ll*a*(p-1))%M+M)%M);
}
参考资料: