AGC005D ~K Perm Counting

题意

求长度为(n),不存在(leftvert a_i-i ightvert=k)的排列个数
(n le 2000)
传送门

思路

正着来并不是很好做,于是考虑反着做。
发现如果把限制条件连起来,会变成多条链
例如=考虑(n=7,k=2)会有这样(4)条链:
((1, otoperatorname{3},5, otoperatorname{7}))
((2, otoperatorname{4},6))
(( otoperatorname{1},3, otoperatorname{5},7))
(( otoperatorname{2},4, otoperatorname{6}))
只要链前后的两个数连在一起,就会产生不合法的数对
容斥(f[i][j][0/1])表示在第(i)位,有(j)个数满足(leftvert a_i-i ightvert=k),当前数与前面的有没有连
对于单条的链,(dp)方程还是很显然的:

  • 这个数与后面的连起来(必须保证前面和它没连):(f[i][j][1]+=f[i-1][j-1][0])
  • 不连:(f[i][j][0]+=f[i-1][j][1]+f[i-1][j][0])

那么考虑多条链怎么合并,其实就是链末端往后面连不会有贡献,所以就可以把所有链拼成一条链,(f[链末][j][1])是不可能存在的。只要刚开始做好标记,按照一条链的来做就完成了。
最后容斥即可。

#include <bits/stdc++.h>
const int mu=924844033;
int n,k,f[2][4005][2],cnt,p[2005],a[4005],now,ans;
int main(){
	scanf("%d%d",&n,&k);
	p[0]=1;
	for(int i=1;i<=n;i++) p[i]=p[i-1]*1ll*i%mu;
	for (int i=1;i<=k;i++){
		a[cnt]=-1;
		for(int j=i;j<=n;j+=k) cnt++;
		a[cnt]=-1;
		for(int j=i;j<=n;j+=k) cnt++;
	}
	now=1,f[now][0][0]=1;
	for (int i=1;i<cnt;i++){
		now=now^1;
		memset(f[now],0,sizeof(f[now]));
		for (int j=0;j<=i;j++){
			f[now][j][0]=(f[now][j][0]+f[now^1][j][1]+f[now^1][j][0])%mu;
			if (j && !a[i]) f[now][j][1]=(f[now][j][1]+f[now^1][j-1][0])%mu;
		}
	}
	ans=p[n];
	for (int i=1;i<=n;i++){
		ans=(ans+(f[now][i][0]+f[now][i][1])*(i&1?-1:1)*1ll*p[n-i]%mu+mu)%mu;
	}
	printf("%d
",ans);
}

后记

想不到的神仙(dp),好短啊。我好菜啊

原文地址:https://www.cnblogs.com/flyfeather6/p/11904209.html