AtCoder AGC 005D 容斥+二分图+DP

本文探讨了一种特定排列问题的解决方法,该问题要求计算满足特定条件的排列数量。通过对错排理论的应用及容斥原理,将问题转化为求解二分图中完美匹配的数量。文章详细介绍了如何通过动态规划解决这一问题,并给出了完整的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给定k,求有多少个n的排列,满足对于任意i,|a[i]−i|≠k
n<=2000

比较巧妙的一道题,考试一直刚这道题结果把自己刚死了。。。

根据类似错排的推法&容斥,我们可以得到答案为:
ans=ans+f[i](n-i)!(-1)^i (i=0,1,2…n)
其中fi表示有i个不合法位置的方案数

我们考虑怎么求fi

我们可以构建一个二分图,从左边的i向右边的i+k,i-k连边,可以发现一个完美匹配对应着一个排列。fi就转换成了大小为i(边数)的完美匹配数。我们可以发现对于这个图而言,其实是由k条不相交的路径构成的,我们可以把这些路径连在一起,成一条链来进行DP,但是我们要注意的就是路径的连接点。如果是连接点的话,匹配边数是不会增加的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 4010
#define ll long long
using namespace std;
const ll modd=924844033;
int n,k;
void file(){
    freopen("C.in","r",stdin);
    freopen("C.out","w",stdout);
}
int vis[maxn][2];
int dp[maxn][maxn][2];
ll f[maxn];
ll ans;
ll fac[maxn];
int tag[maxn];int tot;
int main(){
    file();
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        for(int j=0;j<=1;j++){
            if(!vis[i][j]){
                int x=i,y=j;int len=0;
                while(x<=n){
                    vis[x][y]=1;
                    x+=k;y^=1;
                    len++;
                }
                tot+=len;
                tag[tot]=1;
            }
        }
    }
    dp[1][0][0]=1;//printf("%d\n",tot);
    for(int i=1;i<=tot;i++){
        for(int j=0;j<=i;j++){
            dp[i+1][j][0]=(dp[i][j][0]+dp[i][j][1])%modd;//if(dp[i+1][j][0]<0)printf("1*\n");
            if(!tag[i]){
                dp[i+1][j+1][1]=dp[i][j][0];//if(dp[i][j][0]<0)printf("*\n");
            }
        }
    }
    for(int i=1;i<=n;i++){
    //   ll tp=(dp[tot][i][0]+dp[tot][i][1]);
        f[i]=(ll)(dp[tot][i][0]+dp[tot][i][1])%modd;
//      if(f[i]<0)printf("3*\n");
    }
    fac[0]=1;
    for(int i=1;i<=n;i++){
        fac[i]=fac[i-1]*(ll)i%modd;
//      if(fac[i]<0)printf("4*\n");
    }
    ans=fac[n];
    for(int i=1,j=-1;i<=n;i++,j=-j){
        ll tp=f[i]*fac[n-i]%modd;//if(tp<0)printf("5*\n");
        if(j==1)
        ans=(ans+modd+tp)%modd;
        else ans=(ans-tp+modd)%modd;
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值