1. 程式人生 > >Codefoces 382E Ksenia and Combinatorics - 動態規劃

Codefoces 382E Ksenia and Combinatorics - 動態規劃

net border sla 神奇 大於 問題 span def accepted

題目傳送門

  這是一個通往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。

  然後再來考慮一個問題:如何處理算重的情況?

  1. 欽定左子樹大小小於等於右子樹
  2. 當左子樹大小等於右子樹的時候,欽定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 - 動態規劃