区间dp2

[Algo] 区间dp2

1. 合唱队

// 1. 合唱队
// https://www.luogu.com.cn/problem/P3205
struct CountStruct
{
    int cntLeft = 0;
    int cntRight = 0;
};
int compute(vector<int> &h)
{
    int n = h.size();
    vector<vector<CountStruct>> dp(n, vector<CountStruct>(n));
    for (int i = 0; i < n - 1; i++)
    {
        if (h[i] < h[i + 1])
        {
            dp[i][i + 1].cntLeft = 1;
            dp[i][i + 1].cntRight = 1;
        }
    }
    for (int l = n - 3; l >= 0; l--)
    for (int r = l + 2; r < n; r++)
    {
        CountStruct tmp = dp[l + 1][r];
        int a = tmp.cntLeft, b = tmp.cntRight;
        tmp= dp[l][r - 1];
        int c = tmp.cntLeft, d = tmp.cntRight;
        int cntL = 0, cntR = 0;
        if (h[l] < h[l + 1]) cntL = (cntL + a) % MOD;
        if (h[l] < h[r]) cntL = (cntL + b) % MOD;
        if (h[r] > h[l]) cntR = (cntR + c) % MOD;
        if (h[r] > h[r - 1]) cntR = (cntR + d) % MOD;
        dp[l][r].cntLeft = cntL;
        dp[l][r].cntRight = cntR; 
    }
    return (dp[0][n - 1].cntLeft + dp[0][n - 1].cntRight) % MOD;
}

2. 移除盒子

// 2. 移除盒子
// https://leetcode.cn/problems/remove-boxes/
int removeBoxes(vector<int>& boxes) {
    int n = boxes.size();
    vector<vector<vector<int>>> dp(n, vector<vector<int>>(n, vector<int>(n, 0)));
    return func(boxes, 0, n - 1, 0, dp);
}
// boxes[l....r]范围上要去消除,前面跟着k个连续的和boxes[l]颜色一样的盒子
// 这种情况下,返回最大得分
int func(vector<int>& boxes, int l, int r, int k, vector<vector<vector<int>>>& dp) {
    if (l > r) {
        return 0;
    }
    if (dp[l][r][k] > 0) {
        return dp[l][r][k];
    }
    int s = l;
    while (s + 1 <= r && boxes[s + 1] == boxes[l]) {
        s++;
    }
    int cnt = k + (s - l + 1);
    // 可能性1 : 前缀先消
    int ans = cnt * cnt + func(boxes, s + 1, r, 0, dp);
    // 可能性2 : 讨论前缀跟着哪个后,一起消掉
    for (int m = s + 2; m <= r; m++) {
        if (boxes[m] == boxes[l] && boxes[m - 1] != boxes[m]) {
            // boxes[l] == boxes[m]是必须条件
			// boxes[m - 1] != boxes[m]是剪枝条件,避免不必要的调用
            ans = max(ans, func(boxes, s + 1, m - 1, 0, dp) + func(boxes, m, r, cnt, dp));
        }
    }
    
    dp[l][r][k] = ans;
    return ans;
}

3. 统计不同回文子序列

// 3. 统计不同回文子序列
// https://leetcode.cn/problems/count-different-palindromic-subsequences/description/
int countPalindromicSubsequences(string str) {
    const int mod = 1000000007;
    int n = str.size();
    vector<int> last(256, -1);
    vector<int> left(n), right(n);
    // left[i] : i位置的左边和s[i]字符相等且最近的位置在哪,不存在就是-1
    for (int i = 0; i < n; i++) {
        left[i] = last[str[i]];
        last[str[i]] = i;
    }
    // right[i] : i位置的右边和s[i]字符相等且最近的位置在哪,不存在就是n
    fill(last.begin(), last.end(), n);
    for (int i = n - 1; i >= 0; i--) {
        right[i] = last[str[i]];
        last[str[i]] = i;
    }
    // dp[i][j] : i...j范围上有多少不同的回文子序列
	// 如果i>j,那么认为是无效范围dp[i][j] = 0
    vector<vector<long>> dp(n, vector<long>(n, 0));
    for (int i = 0; i < n; i++) {
        dp[i][i] = 1;
    }
    
    for (int i = n - 2; i >= 0; i--) {
        for (int j = i + 1; j < n; j++) {
            if (str[i] != str[j]) {
                dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1] + mod;
            } else {
                int l = right[i];
                int r = left[j];
                if (l > r) {
                    // i...j的内部没有s[i]字符
                    dp[i][j] = dp[i + 1][j - 1] * 2 + 2;
                } else if (l == r) {
                    // i...j的内部有且仅有一个s[i]字符
                    dp[i][j] = dp[i + 1][j - 1] * 2 + 1;
                } else {
                    // i...j的内部至少有两个s[i]字符
                    dp[i][j] = dp[i + 1][j - 1] * 2 - dp[l + 1][r - 1] + mod;
                }
            }
            dp[i][j] %= mod;
        }
    }
    return (int)dp[0][n - 1];
}
posted @ 2025-03-17 12:24  yaoguyuan  阅读(9)  评论(0)    收藏  举报