区间dp

区间dp

给你两个长度分别 n 和 m 的整数数组 nums 和 multipliers ,其中 n >= m ,数组下标 从 1 开始 计数。

初始时,你的分数为 0 。你需要执行恰好 m 步操作。在第 i 步操作(从 1 开始 计数)中,需要:

选择数组 nums 开头处或者末尾处 的整数 x 。
你获得 multipliers[i] * x 分,并累加到你的分数中。
将 x 从数组 nums 中移除。
在执行 m 步操作后,返回 最大 分数。

示例 1:

输入:nums = [1,2,3], multipliers = [3,2,1]
输出:14
解释:一种最优解决方案如下:

  • 选择末尾处的整数 3 ,[1,2,3] ,得 3 * 3 = 9 分,累加到分数中。
  • 选择末尾处的整数 2 ,[1,2] ,得 2 * 2 = 4 分,累加到分数中。
  • 选择末尾处的整数 1 ,[1] ,得 1 * 1 = 1 分,累加到分数中。
    总分数为 9 + 4 + 1 = 14 。
    示例 2:

输入:nums = [-5,-3,-3,-2,7,1], multipliers = [-10,-5,3,4,6]
输出:102
解释:一种最优解决方案如下:

  • 选择开头处的整数 -5 ,[-5,-3,-3,-2,7,1] ,得 -5 * -10 = 50 分,累加到分数中。
  • 选择开头处的整数 -3 ,[-3,-3,-2,7,1] ,得 -3 * -5 = 15 分,累加到分数中。
  • 选择开头处的整数 -3 ,[-3,-2,7,1] ,得 -3 * 3 = -9 分,累加到分数中。
  • 选择末尾处的整数 1 ,[-2,7,1] ,得 1 * 4 = 4 分,累加到分数中。
  • 选择末尾处的整数 7 ,[-2,7] ,得 7 * 6 = 42 分,累加到分数中。
    总分数为 50 + 15 - 9 + 4 + 42 = 102 。

这是力扣上的一道题,今天听y总的课补的,问题其实很简单,就是每次取一段区间的左端点或者右端点做运算,不断的dp即可。

ac代码如下:

class Solution {
public:
    int maximumScore(vector<int>& w, vector<int>& c) {
        int n = w.size(), m = c.size();
        //因为只可能取左边m个和右边m个,因此中间的n-2m个可以直接删除。
        if(n >= 2*m){
            int x = m, y = n - m;
            while(y < n) w[x ++] = w[y ++];
            n = x;
        }
        vector<vector<int>>f(n + 2, vector<int>(n + 2));
        for(int len = n - m + 1; len <= n; len++){//先枚举区间长度,再枚举区间左端点。
            for(int l = 1; l + len - 1 <= n; l++){
                int r = l + len - 1;
                f[l][r] = max(f[l+1][r] + w[l-1]*c[n-len], f[l][r-1]+w[r-1]*c[n-len]);//从左端点拿和从右端点拿的最大值。
            }
        }
        return f[1][n];
    }
};

当然还看到了另一种解法:
dp[i][j] : nums开头取i个,末尾取j个的最大得分
k = i + j :代表取的总数
遍历 k
状态转移:

i == 0 : 都是取末尾
dp[i][k - i] = dp[i][k - i - 1] + nums[n - k + i] * multipliers[k - 1];
i == k : 都是取前面
dp[i][k - i] = dp[i - 1][k - i] + nums[i - 1] * multipliers[k - 1];
其他情况 : 取前面与取末尾的比较,取较大者
dp[i][k - i] = max(dp[i][k - i - 1] + nums[n - k + i] * multipliers[k - 1], dp[i - 1][k - i] + nums[i - 1] * multipliers[k - 1]);
结果 : k == m 时, dp[i][j]的最大值。 (m == multipliers.size())
时间复杂度:O(n ^2)
空间复杂度:O(n ^ 2)

代码如下:

class Solution {
public:
    int maximumScore(vector<int>& nums, vector<int>& multipliers) {
        vector<vector<long long>> dp(1005, vector<long long>(1005, 0));//这里的1005可以改为2*m但不能改为2*n,否则会超时
        long long m = multipliers.size(), res = INT_MIN, n = nums.size();
        for(int k = 1; k <= m; ++k){
            for(int i = 0; i <= k; i++){
                if(i == 0) dp[i][k - i] = dp[i][k - i - 1] + nums[n - k + i] * multipliers[k - 1];
                else if(i == k) dp[i][k - i] = dp[i - 1][k - i] + nums[i - 1] * multipliers[k - 1];
                else dp[i][k - i] = max(dp[i][k - i - 1] + nums[n - k + i] * multipliers[k - 1], dp[i - 1][k - i] + nums[i - 1] * multipliers[k - 1]);
                if(k == m) res = max(res, dp[i][k - i]);
            }
        }
        return res;
    }
};


479. 加分二叉树(acwing)

设一个n个节点的二叉树tree的中序遍历为(1,2,3,…,n),其中数字1,2,3,…,n为节点编号。

每个节点都有一个分数(均为正整数),记第i个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:

subtree的左子树的加分 × subtree的右子树的加分 + subtree的根的分数

若某个子树为空,规定其加分为1。叶子的加分就是叶节点本身的分数,不考虑它的空子树。

试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。

要求输出:

(1)tree的最高加分

(2)tree的前序遍历

输入格式
第1行:一个整数n,为节点个数。

第2行:n个用空格隔开的整数,为每个节点的分数(0<分数<100)。

输出格式
第1行:一个整数,为最高加分(结果不会超过int范围)。

第2行:n个用空格隔开的整数,为该树的前序遍历。如果存在多种方案,则输出字典序最小的方案。

数据范围
n<30
输入样例:
5
5 7 1 2 10
输出样例:
145
3 1 2 4 5

本题思路就是根据根节点所在的位置k对整棵树进行划分,将整棵树分为左子树和右子树,然后不断划分,一直到len = 1(即最终到达叶子结点),用·一个数组g[l][r]存储(l, r)的根节点, 用数组f[l][r]存储从l到r的加分,当加分可以更新时,即f[l][r]<w[k]+right*left时,同时更新两个数组。最后我们还需要输出字典序最小的方案,也就是优先保证根节点的编号最小,也就意味着我们首先要找到最左边的最大值,而我们是从小到大枚举区间左端点的,而且只有当f[l][r] 严格小于score的时候才更新,等于的时候不会更新,因此我们最开始恰好就是存储了最左边的最大值,所以我们必定能保证字典序,即得到根节点的最小编号。当我们把根节点输出之后,会优先输出左子树,字典序最小也就是让左子树的根节点编号最小,而这刚好就是前一个步骤的子问题,我们只需要递归输出就行了。

AC代码如下:

#include<iostream>
using namespace std;

const int N = 35;
int f[N][N], g[N][N], w[N];
void print(int l, int r){
    if(l > r) return ;
    int k = g[l][r];
    cout << k << ' ';
    print(l, k - 1);
    print(k+1, r);
}
int main(){
    int n; cin >> n;
    for(int i = 1; i <= n; i++) cin >> w[i];
    
    for(int len = 1; len <= n; len++){
        for(int l = 1; l + len - 1 <= n; l++){
            int r = l + len - 1;
            if(len == 1){
                f[l][r] = w[l];
                g[l][r] = l;
            }
            else{
                for(int k = l; k <= r; k++){
                    int left = k == l? 1 : f[l][k-1];
                    int right = k == r? 1 : f[k+1][r];
                    int score = w[k] + right * left;
                    if(f[l][r] < score){
                        f[l][r] = score;
                        g[l][r] = k;
                    }
                }
            }
        }
    }
    cout << f[1][n] << endl;
    print(1, n);
}

1771. 由子序列构造的最长回文串的长度

给你两个字符串 word1 和 word2 ,请你按下述方法构造一个字符串:

从 word1 中选出某个 非空 子序列 subsequence1 。
从 word2 中选出某个 非空 子序列 subsequence2 。
连接两个子序列 subsequence1 + subsequence2 ,得到字符串。
返回可按上述方法构造的最长 回文串 的 长度 。如果无法构造回文串,返回 0 。

字符串 s 的一个 子序列 是通过从 s 中删除一些(也可能不删除)字符而不更改其余字符的顺序生成的字符串。

回文串 是正着读和反着读结果一致的字符串。

示例 1:

输入:word1 = “cacb”, word2 = “cbba”
输出:5
解释:从 word1 中选出 “ab” ,从 word2 中选出 “cba” ,得到回文串 “abcba” 。
示例 2:

输入:word1 = “ab”, word2 = “ab”
输出:3
解释:从 word1 中选出 “ab” ,从 word2 中选出 “a” ,得到回文串 “aba” 。
示例 3:

输入:word1 = “aa”, word2 = “bb”
输出:0
解释:无法按题面所述方法构造回文串,所以返回 0 。

提示:

1 <= word1.length, word2.length <= 1000
word1 和 word2 由小写英文字母组成

这是力扣周赛的一道题,比赛时没有想出来,赛后补题才发现其实这是一个很简单的区间dp, 先将给定的两个字符串相加,然后判断每一个区间内的最长回文串,只有当取出来的两个字母分别属于两个字符串时才更新结果。

const int N =2010;
int dp[N][N];
class Solution {
public:
    int longestPalindrome(string a, string b) {
        int n = a.size(), m = b.size(), len = n + m;
        string w = a + b;
        int res = (w[n-1] == w[n]) ? 2 : 0;
        for(int i = 0; i < len; i++){
            dp[i][i] = 1;
        }
        for(int i = 0; i < len - 1; i++){
            dp[i][i+1] = (w[i] == w[i+1]) ? 2 : 1;
        }
        for(int l = 2; l < len; l++){
            for(int i = 0; i + l < len; i++){
                int j = i + l;
                if(w[i] == w[j]){
                    dp[i][j] = dp[i+1][j-1] + 2;
                    //若i j 分别属于两个字符串,才更新最终结果
                    if(i < n && j >= n) res = max(dp[i][j], res);
                }
                else dp[i][j] = max(dp[i+1][j], dp[i][j - 1]);
            }
        }
        return res;
    }
};
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追梦_赤子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值