3n块披萨

给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨
每选一块,周围两块会被其他人分掉,请你返回你可以获得的披萨大小总和的最大值。

1. 动态规划

容易证明,可以选出任意一个长度为n的不相邻的数字序列
dp[i][j]表示前i个元素中,选择不相邻的j个元素最大值

class Solution {
public:
    int maxSizeSlices(vector<int>& slices) {
        int n = slices.size(); int k = n/3;
        int dp[n][k+1];//动态规划,dp[i][j]表示前i个元素中,选择不相邻的j个元素最大值 
        function<int(int)> f = [&](int flag)->int{
            memset(dp,0,sizeof(dp));
            dp[0][1] = slices[0+flag];
            dp[1][1] = max(slices[0+flag],slices[1+flag]);//必须初始化,避免越界
            for(int i=2;i<n-1;i++)//遍历n-1个数,已经初始化两个
                for(int j=1;j<=(i+2)/2&&j<=k;j++)//遍历已选个数范围,保证无后效性
                    dp[i][j] = max(dp[i-1][j],dp[i-2][j-1]+slices[i+flag]);//选与不选
            return dp[n-2][k];
        };
        return max(f(0), f(1));
    }
};

2. 反悔贪心

贪心选择最大的元素,同时将周围两个数合并掉(因为不能选)
合并后的数为反悔的契机,值为临近两数和减去当前选取的元素,在之后的过程中,选取该数,即意味着反悔,同时不影响后续的选取
这里为了简洁,不使用优先队列维护最大值,因为比较麻烦
优先队列在取出最大值后,需要得到最大值下标,以及其左右的元素,进行合并

class Solution {
public:
    int maxSizeSlices(vector<int>& slices) {
        int n = slices.size();
        int need = n / 3;
        int ret = 0;
        for (int i = 0; i < need; i++) {
            int max_pos = max_element(slices.begin(), slices.end()) - slices.begin();
            int max_left = (max_pos + slices.size() - 1) % slices.size();
            int max_right = (max_pos + slices.size() + 1) % slices.size();
            int dis = slices[max_left] + slices[max_right] - slices[max_pos];
            ret += slices[max_pos];
            slices[max_pos] = dis;
            slices.erase(slices.begin() + max(max_left, max_right));
            slices.erase(slices.begin() + min(max_left, max_right));
        }
        return ret;
    }
};

3. 反悔贪心 + 优先队列

class Solution {
public:
    int maxSizeSlices(vector<int>& slices) {
        int n = slices.size(); int k = n/3;
        int res = 0;
        vector<vector<int>> nums(n,vector<int>(4)); //每个vector代表一个节点,值分别为权重,左节点索引,右节点索引,自身索引
        priority_queue<vector<int>> pq;//其实这里只需要权值和位置,为了统一节点,全部弄上去
        for(int i=0;i<n;i++){
            nums[i][0] = slices[i];
            nums[i][1] = (i-1+n)%n; //左指针
            nums[i][2] = (i+1+n)%n; //右指针
            nums[i][3] = i;//记录一下位置
            pq.push(nums[i]);
        }
        unordered_set<int> s;//记录被合并掉的位置
        int i = 0;
        while(i<k){//贪心选k个最大值
            vector<int> cur = pq.top(); pq.pop(); //不能用这里面左右节点的索引,因为可能更新过
            int pos = cur[3];//最大节点位置
            if(s.count(pos)) continue;//跳过已被合并节点
            vector<int> &left = nums[nums[pos][1]]; //左边节点
            vector<int> &right = nums[nums[pos][2]]; //右边节点
            //改变值
            res = res + cur[0];//选取
            i++;//计数增加
            nums[pos][0] = left[0] + right[0] - cur[0]; //原位置更新新的反悔节点
            //改变索引
            nums[left[1]][2] = pos;
            nums[right[2]][1] = pos;
            nums[pos][1] = left[1];
            nums[pos][2] = right[2];
            //节点放回优先队列
            pq.push(nums[pos]);
            //剔除相邻节点
            s.insert(left[3]);
            s.insert(right[3]);
        }
        return res;
    }
};
posted @ 2023-08-18 02:38  失控D大白兔  阅读(23)  评论(0编辑  收藏  举报