AGC 005D.~K Perm Counting(容斥 DP 二分圖)
阿新 • • 發佈:2018-12-10
\(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\)
如果只有一條長\(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; }