AtCoder AGC019E Shuffle and Swap (DP、FFT、多项式求逆、多项式快速幂)
題目鏈接
https://atcoder.jp/contests/agc019/tasks/agc019_e
題解
tourist的神仙E題啊做不來做不來……這題我好像想歪了啊= =……
首先我們可以考慮,什么樣的操作序列才是合法的?
有用的位置只有兩種,一種是兩個序列在這個位置上都是1, 稱作11型,另一種是一個0一個1, 稱作01型。設兩種位置分別有\(A\)個和\(2B\)個。
考慮一個操作序列,交換兩個11型相當于沒交換,每個11型只會被交換兩次,每個01型只會被交換一次。這也就是說,如果我們從shuffle之后的\(a_i\)向\(b_i\)連邊,那么形成的圖一定是若干個環加上\(m\)條鏈,鏈的開頭結尾都是01型,中間是11型。對于鏈來說,上面操作的順序必須固定;對于環來說,上面操作的順序可以任意。
下面有兩種處理方式。
做法一
設\(dp[i][j]\)表示把\(i\)個無標號的11型放到\(j\)條鏈中,可得DP式: \(dp[i][j]=\sum_{k\le 0}\frac{dp[i-1][j-k]}{(k+1)!}\), 其中分母的含義是鏈上\((u+1)\)個點順序固定,最后的答案是\(A!B!(A+B)!\sum^A_{i=0}dp[B][i]\). \((A+B)!\)表示將邊隨意排序,\(A!B!\)表示11型和01型點之間是有標號的。
時間復雜度\(O(n^3)\).
但是我們發現這個DP就相當于在給多項式\(\sum_{n\le 0}\frac{1}{(n+1)!}x^n\)進行冪運算,于是用多項式快速冪加速即可,時間復雜度\(O(n\log^2n)\)或\(O(n)\).
做法二
有沒有聰明一點的做法?有!
設\(dp[i][j]\)表示目前一共放了\(i\)個11型和\(j\)個01型鏈(考慮已經放了的元素的標號,但是每次僅僅是往右添加),我們強行轉移!
\(dp[i][j]=j^2\times dp[i][j-1]+ij\times dp[i-1][j]\)
前一個式子是要加一個新的01型鏈,選兩個01型;后一個式子是要選一個鏈,再把這條鏈的結尾端點任意擴展一個位置。
答案就是\(\sum_{k}{A\choose k}\times f[k][B]\times ((A-k)!)^2\times {A+B\choose A-k}\), 其中\(A+B\choose A-k\)是選出位置,\(A\choose k\)是選出編號,\(((A-k)!)^2\)是求出組成環的方案數。
時間復雜度\(O(n^2)\), 可以通過。
但是我們發現這個DP還可以用多項式優化!
令\(g[i][j]=\frac{dp[i][j]}{(j!)^2i!}\), 顯然有\(g[i][j]=g[i][j-1]+j\times g[i-1][j]\)
然后這個使用NE Lattice Path的方式來理解,就是從\((0,0)\)走到\((i,j)\),每往上走一次路徑權值乘上橫坐標,求所有路徑權值和。
考慮另一種DP,枚舉在第\(i\)行走幾步,那么發現第\(i\)列的生成函數就是\(\frac{1}{1-ix}\), 然后答案就是所有列生成函數之積
于是可以分治NTT+多項式求逆計算,時間復雜度\(O(n\log^2n)\).
代碼
做法二\(O(n^2)\)
#include<cstdio> #include<cstdlib> #include<iostream> #include<cassert> #include<cstring> #define llong long long using namespace std;const int N = 2e4; const int P = 998244353; const llong INV2 = 499122177; llong fact[N+3],finv[N+3];llong quickpow(llong x,llong y) {llong cur = x,ret = 1ll;for(int i=0; y; i++){if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}cur = cur*cur%P;}return ret; } llong mulinv(llong x) {return quickpow(x,P-2);} llong comb(llong x,llong y) {return x<0||y<0||x<y ? 0ll : fact[x]*finv[y]%P*finv[x-y]%P;}int n,a,b; char s[N+3],t[N+3]; llong dp[2][N+3];int main() {fact[0] = 1ll; for(int i=1; i<=N; i++) fact[i] = fact[i-1]*i%P;finv[N] = quickpow(fact[N],P-2); for(int i=N-1; i>=0; i--) finv[i] = finv[i+1]*(i+1)%P;scanf("%s%s",s+1,t+1); n = strlen(s+1);for(int i=1; i<=n; i++){if(s[i]=='1' && t[i]=='1') {a++;}else if(s[i]^t[i]) {b++;}}b>>=1;int cur = 0,prv = 1;dp[0][0] = 1ll;for(int j=1; j<=b; j++){cur^=1; prv^=1;dp[cur][0] = dp[prv][0]*j*j%P;for(int i=1; i<=a; i++){dp[cur][i] = (dp[prv][i]*j*j+dp[cur][i-1]*i*j)%P; // printf("dp[%d][%d]=%lld\n",i,j,dp[cur][i]);}}llong ans = 0ll;for(int k=0; k<=a; k++){ans = (ans+dp[cur][k]*comb(a,k)%P*fact[a-k]%P*fact[a-k]%P*comb(a+b,a-k))%P; // printf("k%d ans%lld\n",k,ans);}printf("%lld\n",ans);return 0; }總結
以上是生活随笔為你收集整理的AtCoder AGC019E Shuffle and Swap (DP、FFT、多项式求逆、多项式快速幂)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces Gym 10163
- 下一篇: AtCoder AGC031D A Se