loj3395. 「2020-2021 集训队作业」Yet Another Permutation Problem

明明不难,为啥做出来还是这么高兴呢?我是不是太容易高兴了。

枚举 \(k\),考虑一个排列能在 \(k\) 步内还原的条件。我们会剥掉一个前缀一个后缀共 \(k\) 个,只要剩下的是上升的,我们就可以把剥掉的插进去还原。换句话说,只要最长的连续上升的区间长度不小于 \(n-k\) 就行。容斥,计算每个连续上升不大于 \(k\) 的排列个数。

排列,上升,仍然考虑容斥,考虑 \(dp\) 出每个长度的容斥系数,然后计算 \(f_{i,j}\) 表示现在考虑 \((i,i+1)\) 之间是否钦定为上升,现在已经连续了 \(j\) 个上升。转移是平凡的。这就是三次方的做法。

把容斥系数打出来,发现只有在模 \(k+1\)\(0,1\) 的时候有值,且分别为 \(-1,1\),于是就做完了。时间复杂度 \(O(n^2\log n)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
#define inf 1e9
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
const int N=1005;
int n,mod,fac[N],C[N][N],inv[N],ifc[N],f[N],Sf[N],Ans[N],dp[N][N],g[N];
inline void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
inline void sub(int &x,int y){x=(x<y?x-y+mod:x-y);}
int main(){
//	freopen("permutation.in","r",stdin);
//	freopen("permutation.out","w",stdout);
	n=read(),mod=read();
	fac[0]=C[0][0]=inv[1]=ifc[0]=1;
	for(int i=1;i<=n;i++){
		C[i][0]=1;fac[i]=1ll*fac[i-1]*i%mod;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	}for(int i=2;i<=n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=n;i++)ifc[i]=1ll*ifc[i-1]*inv[i]%mod;
	Ans[n-1]=fac[n];Ans[0]=1;
	for(int k=1;k<n;k++){
		for(int i=0;i<=n+1;i++)f[i]=0;f[1]=fac[n];
		for(int i=1;i<=n;i++)
			for(int j=0;i+j*(k+1)<=n;j++){
				int bas=i+j*(k+1);
				if(bas+1<=n+1)f[bas+1]=(f[bas+1]+1ll*f[i]*ifc[bas+1-i])%mod;
				if(bas+k+1<=n+1)f[bas+k+1]=(f[bas+k+1]-1ll*f[i]*ifc[bas+k+1-i]%mod+mod)%mod;
			}
		Ans[n-k-1]=(fac[n]-f[n+1]+mod)%mod;
	}for(int i=0;i<n;i++)printf("%d\n",Ans[i]);
	return 0;
}

深深地感到自己的弱小。

posted @ 2023-07-07 09:14  syzf2222  阅读(91)  评论(1编辑  收藏  举报