题解 AT2062 【[AGC005D] ~K Perm Counting】
首先正面做不太好做,考虑容斥。
设
那么答案就是
原题可以看成一个二分图的形式:(
左边是排列的编号,右边是权值,那么现在要做的就是连
那么考虑什么时候会出现
如图,当
我们考虑选出一些边,而且任意两条边都不能接在同一个端点上(因为每个点的度数要为
发现图中的边构成了若干条链且互不影响,于是把他们拿出来铺平:
此时如果要使得
设
那么容易得到:
但是还有一种特殊情况,那就是
所以我们需要开一个数组判断一下某一个点是否是链的开头。
按着这个 dp,那么有
意思就是先把满足有
代码如下:
#include<bits/stdc++.h>
#define N 2010
#define ll long long
#define mod 924844033
using namespace std;
int n,k,tot,a[N];
ll fac[N],dp[N<<1][N][2];
bool vis[N<<1];
int main()
{
scanf("%d%d",&n,&k);
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
for(int i=1;i<=k;i++)
{
for(int t=0;t<2;t++)
{
for(int j=i;j<=n;j+=k)
{
tot++;
if(i!=j) vis[tot]=1;
}
}
}
dp[0][0][0]=1;
for(int i=1;i<=(n<<1);i++)
{
for(int j=0;j<=n;j++)
{
dp[i][j][0]=(dp[i-1][j][0]+dp[i-1][j][1])%mod;
if(vis[i]&&j) dp[i][j][1]=dp[i-1][j-1][0];
}
}
ll ans=0;
for(int i=0;i<=n;i++)
{
if(i&1) ans=(ans-(dp[n<<1][i][0]+dp[n<<1][i][1])*fac[n-i]%mod+mod)%mod;
else ans=(ans+(dp[n<<1][i][0]+dp[n<<1][i][1])*fac[n-i]%mod)%mod;
}
printf("%lld\n",ans);
return 0;
}