跳轉到

LCS

LCS 介紹

LCS

輸入兩個字串 s, t,長度分別是 n, m,找到 s, t 的最長共同子序列長度

考慮兩個字串的最後一個字元是否可以配對,dp(i, j) 表示只看 s[0~i] 與 t[0~j] 的 LCS 長度,i, j 一定要配在一起,那這樣轉移就會是去枚舉之前的決策點 dp(p, q),轉移耗時 O(n^2)。不過若我們定義 dp(i, j) 表示只看 s[0~i] 與 t[0~j] 的 LCS 長度,而 i, j 不一定要配,那麼我們進而可以得到以下轉移:

  • 若 s[i] == t[j]: dp(i, j) = max{ dp(i, j-1), dp(i-1, j) , dp(i-1, j-1) + 1}

  • 否則:dp(i, j) = max{ dp(i, j-1), dp(i-1, j) }

答案就是 dp(n-1, m-1)。我們可以發現若一個狀態定義改變一下,或許就能更快速的轉移,實現更快的效率。

題目

USACO 2016 DEC Team Building P

給一個長度為 \(n\) 的序列 \(a\),一個長度為 \(m\) 的序列 \(b\),從兩個序列各選出 \(k\) 個元素,滿足 \(a\) 選的最大元素大於 \(b\) 選的最大元素,\(a\) 選的次大元素大於 \(b\) 選的次大元素,以此類推,問有幾種選法。

\(1\le n,m\le 1000, 1\le k\le 10\)

思路

定義 dp(i, j, k) 為考慮 a 的前 i 個,b 的前 j 個,共選了 k 個的方案數。 轉移為

  • 選:if (a[i] > b[j]) dp(i, j, k) += dp(i - 1, j - 1, k - 1)
  • 不選:dp(i, j, k) += dp(p, q, k - 1)

由於 dp(i, j, k) 需要用到所有 dp(p, q, k - 1) 的關係,我們在實作上必須先枚舉 k 這維。轉移式的部分,我們可以使用二維前綴和優化,或者是我們每次枚舉完一個 k 後,我們對於整個 dp(i, j, k) 做一次前綴加總。複雜度 O(nmk)。

把所有牛放在一起從大到小排序,相同則 \(A\) 的牛放在前面,問題變為從序列中選擇一些元素,使得任意前綴 \(A\) 入選的個數大於 \(B\) 入選的個數。

\(dp(i, j, k)\) 表示考慮了前 \(i\) 頭牛,\(A\) 入選了 \(j\) 頭,\(B\)\(A\)\(k\) 頭,轉移就判斷下一頭牛是 \(A\) 的還是 \(B\) 的,如果是 \(B\) 還要判斷當前狀態 \(k\) 是不是等於 \(0\)。複雜度 \(O((n+m)k^2)\)

code1
#include <bits/stdc++.h>
using namespace std;

const int mod = 1e9 + 9;
int a[1005], b[1005], dp[1005][1005][12];

int main() {
    int n, m, p;
    cin >> n >> m >> p;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i++) {
        cin >> b[i];
    }
    sort(a + 1, a + n + 1);
    sort(b + 1, b + m + 1);
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            dp[i][j][0] = 1;
        }
    }
    for (int k = 1; k <= p; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (a[i] > b[j]) {
                    dp[i][j][k] = dp[i - 1][j - 1][k - 1];
                }
            }  
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                dp[i][j][k] = (dp[i][j][k] + dp[i][j - 1][k]) % mod;
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k]) % mod;
            }
        }
    }
    cout << dp[n][m][p];
}
code2
#include <bits/stdc++.h>
using namespace std;

const int mod = 1e9 + 9;
int a[1005], b[1005], dp[1005][1005][12];

int main() {
    int n, m, p;
    cin >> n >> m >> p;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i++) {
        cin >> b[i];
    }
    sort(a + 1, a + n + 1);
    sort(b + 1, b + m + 1);
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            dp[i][j][0] = 1;
        }
    }
    for (int k = 1; k <= p; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k]) % mod;
                dp[i][j][k] = (dp[i][j][k] + dp[i][j - 1][k]) % mod;
                dp[i][j][k] = (dp[i][j][k] - dp[i - 1][j - 1][k] + mod) % mod;
                if (a[i] > b[j]) {
                    dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k - 1]) % mod;
                }
            }  
        }
    }
    cout << dp[n][m][p];
}
code3
#include <bits/stdc++.h>
using namespace std;

struct Node {
    int x, t;

    bool operator<(const Node &rhs) const {
        if (x == rhs.x) {
            return rhs.t < t;
        }
        return rhs.x < x;
    }
} a[2009];

const int mod = 1e9 + 9;
int n, m, p, dp[2009][2009][20];

signed main() {
    cin >> n >> m >> p;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        a[i] = {x, 1};
    }
    for (int i = 1; i <= m; i++) {
        int x;
        cin >> x;
        a[i + n] = {x, 2};
    }
    sort(a + 1, a + 1 + n + m);
    dp[0][0][0] = 1;
    for (int i = 0; i <= n + m; i++) {
        for (int j = 0; j <= min(i, p); j++) {
            for (int k = 0; k <= j; k++) {
                dp[i + 1][j][k] = (dp[i + 1][j][k] + dp[i][j][k]) % mod;
                if (a[i + 1].t == 1) {
                    dp[i + 1][j + 1][k + 1] = (dp[i + 1][j + 1][k + 1] + dp[i][j][k]) % mod;
                }
                if (a[i + 1].t == 2 && k >= 1) {
                    dp[i + 1][j][k - 1] = (dp[i + 1][j][k - 1] + dp[i][j][k]) % mod;
                }
            }
        }
    }
    cout << dp[n + m][p][0] << '\n';
}