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

题意:给定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
#include
#include
#include
#include
#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;
}

你可能感兴趣的:(动态规划)