6830. 【2020.10.25提高组模拟】排列
一个排列,每次可以将一个字符提到开头或结尾,问进行至多\(k\)次后能够形成的排列的方案数。
\(k\)从\(0\)到\(n-1\)的答案都要计算。
\(n\le 1000\)
显然,答案为包含着至少一个长度大于等于\(n-k\)的子段的排列的方案数。
现在变成了统计多少个排列,每个上升子段的长度都小于\(k\)。
以前见过现在不会了。
有种显然错误的方法:\(f_i\)表示长度为\(i\)的合法排列的方案数。转移的时候直接枚举下一段上升子段的长度。很显然由于不知道两个子段是否合在一起变成一个长的上升子段,所以它是错误的。
现在用容斥系数给修正它。设长度为\(i\)的子段的容斥系数为\(f_i\)。考虑对于一个最终的排列,它的其中一个极长的上升子段会被如何贡献到。设\(F(x)\)为\(f\)的生成函数。则有:\(\sum_{i\ge 1} F^i(x)=\sum _{i=1}^{k-1}x^i\)。把\(F(x)\)解出来就好了。
可以发现\(F(x)\)只有\(O(\frac{n}{k})\)项。所以时间复杂度是有保障的。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1010
#define ll long long
int n,mo;
ll C[N][N];
ll f[N],g[N];
ll work(ll k){
memset(g,0,sizeof(ll)*(n+1));
g[0]=1;
for (int i=1;i<=n;++i){
for (int j=0;k*j+1<=i;++j)
(g[i]+=g[i-(k*j+1)]*C[i][k*j+1])%=mo;
for (int j=0;k*j+k<=i;++j)
(g[i]-=g[i-(k*j+k)]*C[i][k*j+k])%=mo;
}
return (g[n]+mo)%mo;
}
int main(){
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
// freopen("permutation.in","r",stdin);
// freopen("permutation.out","w",stdout);
scanf("%d%d",&n,&mo);
for (int i=0;i<=n;++i){
C[i][0]=1;
for (int j=1;j<=i;++j)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo;
}
ll fac=1;
for (int i=1;i<=n;++i)
fac=fac*i%mo;
for (int k=0;k<n;++k)
printf("%lld\n",(fac-work(n-k)+mo)%mo);
return 0;
}