6830. 【2020.10.25提高组模拟】排列

一个排列,每次可以将一个字符提到开头或结尾,问进行至多(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;
}
原文地址:https://www.cnblogs.com/jz-597/p/13893152.html