AGC005D ~K Perm Counting

题意

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

思路

正着来并不是很好做,于是考虑反着做。
发现如果把限制条件连起来,会变成多条链
例如=考虑\(n=7,k=2\)会有这样\(4\)条链:
\((1,\not\operatorname{3},5,\not\operatorname{7})\)
\((2,\not\operatorname{4},6)\)
\((\not\operatorname{1},3,\not\operatorname{5},7)\)
\((\not\operatorname{2},4,\not\operatorname{6})\)
只要链前后的两个数连在一起,就会产生不合法的数对
容斥\(f[i][j][0/1]\)表示在第\(i\)位,有\(j\)个数满足\(\left\vert a_i-i \right\vert=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\n",ans);
}

后记

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

posted @ 2019-11-21 10:56  flyfeather  阅读(134)  评论(0)    收藏  举报