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

来源:互联网 发布:字有几个字节知乎 编辑:程序博客网 时间:2024/06/06 09:51

题意:给定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 longusing 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;}
原创粉丝点击