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;
}
posted @ 2020-10-28 20:14  jz_597  阅读(92)  评论(0编辑  收藏  举报