Codefoces 382E Ksenia and Combinatorics - 動態規劃
題目傳送門
這是一個通往Codeforces的門
這是一個通往vjudge的門
題目大意
問以節點1為根的大小為$n$的帶標號二叉樹有多少個滿足最大匹配數為$k$。答案模$10^{9} + 7$。
樹的最大匹配是指,選擇盡量多的邊,使得這些邊沒有公共點,最大匹配數是這個邊集的大小。
考慮二叉樹的最大匹配怎麽做。
用$f[i]$表示$i$節點被覆蓋的最大匹配數,$g[i]$表示$i$節點未被覆蓋的最大匹配數。
那麽有$f[i] = \max (\max(f[l], g[l]) + f[r], \max (f[r], g[r]) + f[l]) + 1$,$g[i] = \max (f[l], g[l]) + \max (f[r], g[r])$。
然後樹形dp即可。
因此可以設計出這裏的狀態$f[i][j][k]$表示$i$個點的滿足條件二叉樹,選根的最大匹配數為$j$,不選根的最大匹配數為$k$。
但是這麽做時間會炸掉。
必須考慮優化狀態。通過打表找規律其實是看了題解,可以發現一個神奇的事情。
神奇的性質 一棵二叉樹,如果節點數大於1,那麽根被覆蓋要麽比根未被覆蓋的情況的最大匹配數多1要麽和它相等。
證明 要證明它,等價於證明$g[i] \geqslant f[i] - 1$以及$f[i] \geqslant g[i]$。
首先考慮前一個不等式,對於一個根被覆蓋的匹配,我只需去掉根和某個子節點的匹配,然後就變成了一個根未被覆蓋的匹配,但是匹配數只比原來少一,所以$g[i] \geqslant f[i] - 1$。
然後考慮後一個不等式,討論$g[i]$對應的匹配中根節點的子樹,因為節點數大於1,所以根至少存在一個子樹。
- 子樹的根節點在匹配中。那麽斷開匹配它的邊,讓它和根節點之間的邊被匹配。
- 子數的根節點不在匹配中。那麽讓它和根節點質檢的邊匹配。
這樣就證明了$f[i] \geqslant g[i]$。
因此,定理得證。
因此,可以直接去掉$k$,把它變成$0 / 1$,表示根被覆蓋的最大匹配數是否比根未被覆蓋的最大匹配數多1。
然後再來考慮一個問題:如何處理算重的情況?
- 欽定左子樹大小小於等於右子樹
- 當左子樹大小等於右子樹的時候,欽定2號點在左子樹內。
以上很多討論都要求節點數大於2,所以節點數小於等於1的情況直接賦初值。
然後考慮轉移。
枚舉左子樹大小,左右子樹匹配數,轉移的時候特判左子樹是否為空,考慮從左右子樹中重新選取一個點作為新根,以及左右子樹各有哪些點。
具體轉移請看代碼。
Code
1 /** 2 * Codeforces 3 * Problem#382E 4 * Accepted 5 * Time: 16ms 6 * Memory: 2056k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 12 const int N = 55, M = 1e9 + 7; 13 14 int n, k; 15 int C[N][N]; 16 int f[N][N][2]; 17 18 inline void init() { 19 scanf("%d%d", &n, &k); 20 } 21 22 inline void solve() { 23 C[0][0] = 1; 24 for (int i = 1; i <= n; i++) { 25 C[i][0] = C[i][i] = 1; 26 for (int j = 1; j < i; j++) 27 C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % M; 28 } 29 30 f[0][0][0] = 1, f[1][0][0] = 1; 31 for (int i = 2; i <= n; i++) { 32 for (int ls = 0, rs, c, f0, f1, cl; (ls << 1) < i; ls++) { 33 rs = i - 1 - ls; 34 c = ((ls == rs) ? (C[i - 2][ls - 1]) : (C[i - 1][ls])); 35 cl = ((!ls) ? (1) : (ls)); 36 for (int lk = 0; (lk << 1) <= ls; lk++) { 37 for (int rk = 0; (rk << 1) <= rs; rk++) { 38 for (int j = 0, b1, b2, b; j < 4; j++) { 39 b1 = (j & 1), b2 = ((j & 2) >> 1); 40 if (!lk && b1) continue; 41 if (!rk && b2) continue; 42 f0 = lk + rk, f1 = (ls) ? (lk + rk - min(b1, b2) + 1) : (rk - b2 + 1); 43 b = f1 - f0; 44 f[i][f1][b] = (f[i][f1][b] + (f[ls][lk][b1] * 1ll * f[rs][rk][b2] % M * c) % M * cl * rs % M) % M; 45 } 46 } 47 } 48 } 49 } 50 printf("%d\n", (f[n][k][0] + f[n][k][1]) % M); 51 } 52 53 int main() { 54 init(); 55 solve(); 56 return 0; 57 }
Codefoces 382E Ksenia and Combinatorics - 動態規劃