一个排列,每次可以将一个字符提到开头或结尾,问进行至多(k)次后能够形成的排列的方案数。
(k)从(0)到(n-1)的答案都要计算。
(nle 1000)
显然,答案为包含着至少一个长度大于等于(n-k)的子段的排列的方案数。
现在变成了统计多少个排列,每个上升子段的长度都小于(k)。
以前见过现在不会了。
有种显然错误的方法:(f_i)表示长度为(i)的合法排列的方案数。转移的时候直接枚举下一段上升子段的长度。很显然由于不知道两个子段是否合在一起变成一个长的上升子段,所以它是错误的。
现在用容斥系数给修正它。设长度为(i)的子段的容斥系数为(f_i)。考虑对于一个最终的排列,它的其中一个极长的上升子段会被如何贡献到。设(F(x))为(f)的生成函数。则有:(sum_{ige 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
",(fac-work(n-k)+mo)%mo);
return 0;
}