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\),好短啊。我好菜啊
* 生而自由 爱而无畏 *

浙公网安备 33010602011771号