leetcode双周赛22场第四题三种解法详解

题目传送门
本来做这道题是想练一练区间dp的,结果发现这道题区间dp反而麻烦了,但是还是记录一下不一样的写法。
第一种:把这道题转换成从3n长度的环形里选出n个数,让这n个数的和最大。
先把它当成线性,忽略掉环。
dp[i][j][k]表示前i个数中选了j个,第i个有没有选用k来表示。
这样转移方程就变成了:
dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1]);
如果当前这个数不取,我们就拿i - 1中取j个数两种情况中的最大值就行了。
dp[i][j][1] = dp[i - 1][j - 1][0] + tmp[i - 1];
如果当前这个数要取,就取i-1中取j-1个数并且i-1不取的情况。
然后我们来考虑一下环,环给我们的拘束定义无非就是第一个和最后一个不能一起取,那我们就举例两种情况,第一个不要,最后一个不要,然后取最大值就可以了。
ac代码:

const int maxn = 550;
int dp[maxn][maxn / 3][2];
class Solution {
public:
    int Solve(vector<int> &tmp) {
        int n = tmp.size();
        int total =  n / 3 + 1;
        for (int j = 0; j <= total; j++) dp[0][j][0] = dp[0][j][1] = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= total; j++) {
                dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1]);
                dp[i][j][1] = dp[i - 1][j - 1][0] + tmp[i - 1];
            }
        }
        return max(dp[n][total][0], dp[n][total][1]);
    }
    int maxSizeSlices(vector<int>& slices) {
        vector<int> v1(slices.begin() + 1, slices.end());
        vector<int> v2(slices.begin(), slices.end() - 1);
        return max(Solve(v1), Solve(v2));
    }
};

第二种方法:算是第一种的优化,我们把dp数组降到二维,dp[i][j],代表前i个数取了j个数的情况,这样转移方程就变成了:
dp[i][j] = max(dp[i - 1][j], dp[i - 2][j - 1])
如果当前不取,就是dp[i - 1][j],如果当前取了,就是dp[i - 2][j - 1]。
ac代码:

class Solution {
public:
    int calculate(const vector<int>& slices) {
        int n = slices.size();
        int choose = (n + 1) / 3;
        vector<vector<int>> dp(n + 1, vector<int>(choose + 1));
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= choose; ++j) {
                dp[i][j] = max(dp[i - 1][j], (i - 2 >= 0 ? dp[i - 2][j - 1] : 0) + slices[i - 1]);
            }
        }
        return dp[n][choose];
    }

    int maxSizeSlices(vector<int>& slices) {
        vector<int> v1(slices.begin() + 1, slices.end());
        vector<int> v2(slices.begin(), slices.end() - 1);
        int ans1 = calculate(v1);
        int ans2 = calculate(v2);
        return max(ans1, ans2);
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/pizza-with-3n-slices/solution/3n-kuai-pi-sa-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第三种方法:区间dp,dp[i][j]表示区间[i][j]中取披萨的最大值。
首先我们先考虑状态转移,我们可以想到,所有的状态的区间长度都应该是3的倍数,要取就3个人一起取,然后先不考虑环形的,转移方程式如下:
当我们想求出dp[i][j],那么我们要想能转移到这个状态的几种情况,我想到两种情况。一种:dp[i][j] = dp[i][i + k] + dp[k + 1][j] k->[i + 2, j - 3],这里注意[i,i + k] 和[k + 1,j]的区间长度都是3的倍数。第二种:dp[i][j] = dp[i + 1][k - 1] + slices[k] + dp[k + 1][j - 1]
其实这两种都很好理解:第一种就是很和谐的,把[i,j]中3的倍数长度的区间都直接合起来,比如1,8,2,2,9,1,这种就是dp[0][2]=8,dp[3][5]=9,dp[0][5] = dp[0][2] + dp[3][5] = 17;
另一种就是:2,3,4,5,6,1,我们就可以先拿[1,3]的,剩下2,6,1我们再拿中间的,这样就是dp[i][j] = max(dp[i][j], dp[i + 1][k - 1] + slices[k] + dp[k + 1][j]),k->[i+1,j-1]
然后环形的处理方式,区间dp处理环形的方式,首先想到的就是倍增数组,把1-n的数组变成1-2*n,然后最后求长度为n的区间里的最大值。
这个可以参考我之前的博客:环形石子
最后ac代码:这里主要注意的我觉得就是k的取值吧,我都是直接+3来优化的。

const int maxn = 550;
int dp[maxn<<1][maxn<<1];
class Solution {
public:
    int maxSizeSlices(vector<int>& slices) {
        int n = slices.size();
        for (int i = 0; i < n; i++) slices.push_back(slices[i]);
        for (int i = 0; i + 2 < (n<<1); i++) dp[i][i + 2] = slices[i + 1];
        for (int i = 0; i < (n<<1); i++) for (int j = 0; j <= i + 1; j++) dp[i][j] = 0;

        for (int len = 6; len <= (n<<1); len += 3) {
            for (int i = 0; i + len - 1 < (n<<1); i++) {
                int j = i + len - 1;
                dp[i][j] = 0;
                for (int k = i + 2; k <= j - 3; k += 3) {
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j]);
                }
                
                for (int k = i + 1; k <= j - 1; k += 3) 
                    dp[i][j] = max(dp[i][j], dp[i + 1][k - 1] + slices[k] + dp[k + 1][j - 1]);
                
            }
        }

        int ans = 0;
        for (int i = 0; i < n; i++) ans = max(ans, dp[i][i + n - 1]);
        return ans;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值