~K Perm Counting
Problem Statement
Snuke loves permutations. He is making a permutation of length N.
Since he hates the integer K, his permutation will satisfy the following:
Let the permutation be a1,a2,…,aN. For each i=1,2,…,N, |ai−i|≠K.
Among the N! permutations of length N, how many satisfies this condition?
Since the answer may be extremely large, find the answer modulo 924844033(prime).
Constraints
2≦N≦2000
1≦K≦N−1
Input
The input is given from Standard Input in the following format:
N K
Output
Print the answer modulo 924844033.
Sample Input 1
3 1
Sample Output 1
2
2 permutations (1,2,3), (3,2,1) satisfy the condition.
Sample Input 2
4 1
Sample Output 2
5
5 permutations (1,2,3,4), (1,4,3,2), (3,2,1,4), (3,4,1,2), (4,2,3,1) satisfy the condition.
神题一道……
这无比风骚的操作真是令人震惊和赞叹啊。
题意:
求所有满足条件的1到n的排列a,使得对于任意
思路:
考虑把值和位置之间的关系建成一个二分图。
假定左半的N个点依次代表值1~N,右半的N个点依次代表位置1~N。
那么,一条边(i,j)存在的意义是,位置j处的值是
可以发现所有不合法的情况均包含形如(i,i−k)或(i,i+k)的一条边。
令g[i]表示钦定i个不合法的位置,其他位置未知的方案数。
考虑剩下的位置未知,那么采用容斥,最终结果为:
ans=n!−∑ni=0g[i]∗(n−i)!∗(−1)i
那么剩下的就是如何计算g[i]了。
考虑建出只含所有不合法边的方案数
可以发现这个二分图将会是如下的样子(k=3):(想象相同编号的位置之间存在一条边)
1 4
2 5
3 6
4 1
5 2
6 3
1 4
2 5
3 6
这样的极其有规律的折线便是能找到的所有匹配。
考虑把所有这样的链以任意顺序串起来,一起dp:
令f[i][j][0/1],描述当前在i号节点(左右共2n个),已经选择了j条匹配边,当前点连向链上的下一个点的边是选了(1)还是没选(0)的方案数。
那么可以得到转移方程:
f[i+1][j][0]=f[i][j][0]+f[i][j][1]
f[i+1][j+1][1]=f[i][j][0]
然而考虑到咱现在是把所有链串在一起DP,链与链相交的地方是没有边的。
那么按照把所有连串起来的顺序,记录每个链的结尾,在结尾处强制跳过1的状态的转移即可。
最后g[i]=f[2n][i][0]+f[2n][i][1],大功告成!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=4009;
const int md=924844033;
int n,k;
bool vis[N][N];
int ed[N],tot;
int f[N][N][2];
int g[N],fac[N];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
for(int j=0;j<=1;j++)
if(!vis[i][j])
{
int len=0;
for(int x=i,y=j;x<=n;x+=k,y^=1)
vis[x][y]=1,++len;
ed[tot+=len]=1;
}
f[1][0][0]=1;
for(int i=1;i<=tot;i++)
for(int j=0;j<=n;j++)
{
f[i+1][j][0]=(f[i][j][0]+f[i][j][1])%md;
if(!ed[i])
f[i+1][j+1][1]=f[i][j][0];
}
for(int i=0;i<=n;i++)
g[i]=(f[tot][i][0]+f[tot][i][1])%md;
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=(ll)fac[i-1]*i%md;
int ans=fac[n];
for(int i=1,j=-1;i<=n;i++,j=-j)
ans=((ll)ans+md+(ll)j*fac[n-i]*g[i]%md)%md;
printf("%lld\n",ans);
return 0;
}

本文介绍了一种算法,用于计算长度为N的排列中,满足特定条件(即对于任意位置i,|a[i]−i|≠K)的排列数量。通过构建二分图并运用动态规划方法解决该问题。
387

被折叠的 条评论
为什么被折叠?



