AGC005D ~K Perm Counting 题解
[AGC005D] ~K Perm Counting 题解
如果一个排列 \(P\) 满足对于所有的 \(i\) 都有 \(|P_i-i|\neq k\),则称排列 \(P\) 为合法的。现给出 \(n\) 和 \(k\),求有多少种合法的排列。由于答案很大,请输出答案对 \(924844033\) 取模的结果。
\(2\leq n\leq 2\times 10^3\),\(1\leq k\leq n-1\)。
正着不好做,考虑容斥,设 \(f_i\) 为至少 \(i\) 个位置 \(|P_i-i|=k\) 的方案数,则答案满足
现在就是要求 \(f_i\)。可以考虑转化一下模型,我们将一个数 \(i\) 拆分为位置和值两种点,将位置 \(i\) 与值 \((i-k),(i+k)\) 连边,值 \(i\) 与位置 \((i-k),(i+k)\) 连边,我们最终可以得到若干个独立的链。
容易发现我们如果选择了一条链上的一条边,假设是位置 \(x\) 到值 \((x+k)\),则说明我们将 \(P_x\) 钦定为 \(x+k\),此时我们就不能在选择值 \((x+k)\) 到位置 \((x+2k)\) 的这条边,因为 \(x+k\) 的值已经被使用了。
总结一下就是我们不能选择一条链上两条相邻的边,然后每选择一条边,相当于有一个位置的数不合法。
那么我们将这些链首尾相连存在一个数组里面,定义 \(dp_{i,j,0/1}\) 为前 \(i\) 个点选了 \(j\) 条边,第 \(i\) 个点是否已经在一条被选择的边上的方案数。转移较为简单,具体见代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll P=924844033;
const int N=2003;
ll mul[N],dp[N*2][N*2][2];
int vis[N],lis[N*2],tot,n,k;
int main(){
scanf("%d%d",&n,&k);
mul[0]=1;
for(int i=1;i<=n;++i)mul[i]=mul[i-1]*i%P;
for(int i=1;i<=n;++i){
for(int t=1;t<=2;++t){
if(vis[i]==2)continue;
for(int j=i;j<=n;j+=k){
lis[++tot]=j;
++vis[j];
}
}
}
dp[0][0][0]=1;lis[0]=-114514;
for(int i=1;i<=tot;++i){
for(int j=0;j<=i;++j){
dp[i][j][0]=(dp[i-1][j][0]+dp[i-1][j][1])%P;
if(j&&lis[i]-lis[i-1]==k)dp[i][j][1]=dp[i-1][j-1][0];
}
}
ll ans=0;
for(ll i=0,f=1;i<=n;++i,f=-f){
ans=(ans+f*(dp[tot][i][0]+dp[tot][i][1])*mul[n-i]%P+P)%P;
}printf("%lld\n",ans);
return 0;
}
本文作者:BigSmall_En
本文链接:https://www.cnblogs.com/BigSmall-En/p/18415697
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步