1. 程式人生 > >AGC 005D.~K Perm Counting(容斥 DP 二分圖)

AGC 005D.~K Perm Counting(容斥 DP 二分圖)

題目連結

\(Description\)

給定\(n,k\),求 滿足對於所有\(i\)\(|a_i-i|\neq k\)的排列的個數。
\(2\leq n\leq 2000,\quad 1\leq k\leq n-1\)

\(Solution\)

容斥。則\(Ans=\sum_{i=0}^n(-1)^ig(i)(n-i)!\),其中\(g(i)\)為至少有\(i\)個位置滿足\(|a_i-i|=k\)的排列數。

考慮如何計算\(g(x)\)。每個\(i\)\(i+k\)\(i-k\)連邊,可以得到一張二分圖,\(g(x)\)就是在這張二分圖上選\(x\)個匹配的方案數。

我們還可以發現,圖中的匹配形成了\(2k\)

條互不相交的鏈,且每條鏈上的數模\(k\)相同(也就是模\(k\)不同的數是互不影響的,所以列舉模數就可以得到所有鏈了)。

如果只有一條長\(l\)的鏈,那麼就是對\(l-1\)個點DP,\(f[i][j][0/1]\)表示當前第\(i\)個點,已經選了\(j\)個匹配,這個點選不選(這個點選要求上一個點沒選)。
對於\(2k\)條鏈也是一樣的,只要在鏈之間加一個點,並強制它不能選(\(f[i][j][1]=0\)),就可以把這些鏈合在一起DP啦。

這樣複雜度\(O(n^2)\),然而好多dalao用NTT\(n\log n\)過掉了orz。


因想跑快點(個人習慣)寫的有點亂,程式碼也可以看他的:

http://www.cnblogs.com/wxjor/p/9476998.html

//20ms  256KB
#include <cstdio>
#include <algorithm>
#define mod 924844033
typedef long long LL;
const int N=4005;//2n

int ban[N],f[2][N][2],fac[N];

int main()
{
    int n,K,cnt=0; scanf("%d%d",&n,&K);
    for(int i=1; i<=K; ++i)
    {
        ban[++cnt]=1;
        for(int j=i+K; j<=n; j+=K) ban[++cnt]=0;
        ban[++cnt]=1;//就是兩條鏈啊 
        for(int j=i+K; j<=n; j+=K) ban[++cnt]=0;
    }
    int p=0; f[p][0][0]=1;
    for(int i=0; i<cnt; ++i,p^=1)
        for(int j=0; j<=i; ++j)
        {
            f[p^1][j][0]=(f[p][j][0]+f[p][j][1])%mod;
            if(!ban[i+1]) f[p^1][j+1][1]=f[p][j][0];
            f[p][j][0]=f[p][j][1]=0;
        }
    fac[0]=1;
    for(int i=1; i<=n; ++i) fac[i]=1ll*fac[i-1]*i%mod;
    LL ans=0;
    for(int i=0; i<=n; ++i)
        ans+=i&1?mod-1ll*fac[n-i]*(f[p][i][0]+f[p][i][1])%mod:1ll*fac[n-i]*(f[p][i][0]+f[p][i][1])%mod;
    printf("%lld\n",ans%mod);

    return 0;
}