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

15 篇文章 0 订阅
14 篇文章 0 订阅

Description

在这里插入图片描述

  • n ≤ 1000 n\le1000 n1000

Solution

  • 梅开二度,几个月前才做过相同套路的题目,结果由于对于容斥的理解不够透彻,最后没有想清楚乱推出来的东西的正确性。
  • 首先可以发现一个结论,对于一个排列,找到最长长度为 l e n len len的上升区间,它需要操作的次数是 n − l e n n-len nlen
  • 那么容易转化为对于每一个 k k k,求 m a x l e n ≤ k maxlen\le k maxlenk的排列的个数。
  • 直接模拟DP可以做到 O ( n 4 ) O(n^4) O(n4) O ( n 5 ) O(n^5) O(n5)
  • 一个很 n a i v e naive naive的做法是往当前的排列里面接一些连续上升的段,再直接乘上分配的组合数。
  • 但是显然无法保证头尾会不会连接成更大的段,所以我们对于每一个长度为 l e n len len的段都要给它一个容斥系数 f ( l e n ) f(len) f(len)使得,在分配完标号以后如果组合成长度大于 k k k的段,贡献就是 0 0 0
  • 写成生成函数的形式, F ( x ) F(x) F(x) f f f的生成函数,即

∑ i = 0 i n f F i ( x ) = ∑ i = 1 k x i \sum_{i=0}^{inf}F^i(x)=\sum_{i=1}^kx^i i=0infFi(x)=i=1kxi

1 + ∑ i = 0 i n f F i ( x ) = ∑ i = 0 k x i 1+\sum_{i=0}^{inf}F^i(x)=\sum_{i=0}^kx^i 1+i=0infFi(x)=i=0kxi

1 1 − F ( x ) = x k + 1 − 1 x − 1 \frac{1}{1-F(x)}=\frac{x^{k+1}-1}{x-1} 1F(x)1=x1xk+11

F ( x ) = 1 − x − 1 x k + 1 − 1 F(x)=1-\frac{x-1}{x^{k+1}-1} F(x)=1xk+11x1

F ( x ) = 1 + ( x − 1 ) 1 1 − x k + 1 F(x)=1+(x-1)\frac{1}{1-x^{k+1}} F(x)=1+(x1)1xk+11

F ( x ) = ∑ i ≥ 0 x ( k + 1 ) i + 1 − ∑ i ≥ 1 x ( k + 1 ) i F(x)=\sum_{i\ge0}x^{(k+1)i+1}-\sum_{i\ge1}x^{(k+1)i} F(x)=i0x(k+1)i+1i1x(k+1)i

  • 由于转移函数 F ( x ) F(x) F(x)只有 n k \frac{n}{k} kn项,所以暴力卷积,复杂度是 O ( n ∗ n k ) O(n*\frac{n}{k}) O(nkn)
  • 总复杂度就是 O ( n 2 l o g   n ) O(n^2log\ n) O(n2log n)的。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 1005
#define ll long long 
using namespace std;

int n,mo,i,j,k;
ll fct[maxn],invf[maxn],ans[maxn],f[maxn];

ll ksm(ll x,ll y){
	ll s=1;
	for(;y;y/=2,x=x*x%mo) if (y&1)
		s=s*x%mo;
	return s;
}

int main(){
//	freopen("ceshi.in","r",stdin);
	freopen("permutation.in","r",stdin);
	freopen("permutation.out","w",stdout);
	scanf("%d%d",&n,&mo);
	fct[0]=1;for(i=1;i<maxn;i++) fct[i]=fct[i-1]*i%mo;
	invf[maxn-1]=ksm(fct[maxn-1],mo-2);
	for(i=maxn-2;i>=0;i--) invf[i]=invf[i+1]*(i+1)%mo;
	for(k=1;k<n;k++){
		memset(f,0,sizeof(f));
		f[0]=1;
		for(i=0;i<n;i++) {
			for(j=0;i+(k+1)*j+1<=n;j++)	
				(f[i+(k+1)*j+1]+=f[i]*invf[(k+1)*j+1])%=mo;
			for(j=1;i+(k+1)*j<=n;j++)
				(f[i+(k+1)*j]-=f[i]*invf[(k+1)*j])%=mo;
		}
		ans[k]=f[n]*fct[n]%mo;
	}
	ans[n]=fct[n]; ll s=0;
	for(i=0;i<n;i++){
		(s+=ans[n-i]-ans[n-i-1])%=mo;
		printf("%lld\n",(s+mo)%mo);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值