【OD机试题笔记】分披萨

题目描述

“吃货”和“馋嘴”两人到披萨店点了一份铁盘(圆形)披萨,并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。

但是粗心服务员将披萨切成了每块大小都完全不同奇数块,且肉眼能分辨出大小。

由于两人都想吃到最多的披萨,他们商量了一个他们认为公平的分法:从“吃货”开始,轮流取披萨。

除了第-块披萨可以任意选取以外,其他都必须从缺口开始选。 他俩选披萨的思路不同。

“馋嘴”每次都会选最大块的拨萨,而且“吃货”知道“馋嘴”的想法。

已知披萨小块的数量以及每块的大小,求“吃货”能分得的最大的披萨大小的总和。

输入描述
第1行为一个正整数奇数 N ,表示披萨小块数量。其中 3 ≤ N< 500

接下来的第 2 行到第 N+1 (共 N 行),每行为一个正整数,表示第i块披萨的大小, 1≤i≤N 。

披萨小块从某一块开始,按照一个方向次序顺序编号为 1 ~ N ,每块披萨的大小范围为[1,2147483647]。

输出描述
”吃货“能分得到的最大的披萨大小的总和。

输入:
5
8
2
10
5
7

输出:
19

说明:
此例子中,有 5 块披萨。每块大小依次为 8 、2 、10 、5 、7。
按照如下顺序拿披萨,可以使”吃货拿到最多披萨:
“吃货”拿大小为 10 的披萨
“馋嘴”拿大小为5的披萨
“吃货”拿大小为7 的披萨
“馋嘴”拿大小为 8 的披萨
”吃货“拿大小为2 的披萨
至此,披萨瓜分完毕,”吃货“拿到的披萨总大小为 10+7+2=19
可能存在多种拿法,以上只是其中一种。

思考

本题是环形披萨分块的博弈型动态规划问题,核心是结合“环形拆线性”+“博弈规则下的DP/记忆化搜索”,最大化吃货的总收益,以下是核心逻辑拆解:

一、问题核心特征

  1. 环形结构:披萨是圆形,首次选任意块后,剩余披萨形成线性缺口(只能从缺口两端选);
  2. 博弈规则
    • 吃货先选,首次可任选一块,后续只能从缺口两端选;
    • 馋嘴每次选缺口两端中更大的块(吃货已知该规则,需以此为前提决策);
    • 总块数为奇数,最终吃货比馋嘴多拿1块。

二、通用解题框架(两种等价思路)

思路1:记忆化搜索(递归+缓存)

核心步骤
  1. 环形遍历首次选择:遍历吃货首次选的每一块(i),计算该选择下的最大收益,最终取所有首次选择的最大值;
  2. 递归处理剩余披萨
    • 首次选i后,剩余披萨的缺口两端为i-1i+1(环形取模);
    • 每轮先让馋嘴按“选两端更大块”的规则拿走一块,缩小缺口范围;
    • 吃货再从新的缺口两端选,递归计算后续最大收益(记忆化缓存cache[l][r]避免重复计算);
  3. 终止条件:剩余可拿块数≤1时,收益为0。
关键逻辑
  • cache[l][r]:记录缺口两端为lr时,吃货能拿到的最大收益;
  • 馋嘴的决策:比较list[l]list[r],拿走更大的那块,更新缺口边界;
  • 吃货的决策:在馋嘴拿完后,从新边界两端选,取“拿左+后续收益”和“拿右+后续收益”的最大值。

思路2:动态规划(DP,环形拆线性)

核心步骤
  1. 环形拆线性:遍历吃货首次选择的每一块(i),将剩余披萨(环形)拆分为线性序列[i+1, ..., n-1, 0, ..., i-1]
  2. DP状态定义dp[l][r]表示线性子序列sub[l..r]中,当前决策方能拿到的最大收益;
  3. DP状态转移
    • 子序列长度为1:直接拿走该块,dp[l][r] = sub[l]
    • 偶数步(吃货决策):选两端中“当前块+剩余子序列收益”更大的,dp[l][r] = max(sub[l]+dp[l+1][r], sub[r]+dp[l][r-1])
    • 奇数步(馋嘴决策):馋嘴选两端更大的块,吃货只能拿剩余部分,dp[l][r] = dp[l+1][r](若sub[l]更大)或dp[l][r-1](若sub[r]更大);
  4. 总收益计算:首次选择的块大小 + 剩余线性序列的DP最大值,遍历所有首次选择取最大。
三、核心关键点
  1. 环形转线性:披萨的环形结构仅影响“首次选择后的剩余序列拆分”,后续均按线性缺口处理;
  2. 博弈规则的落地
    • 馋嘴的决策是“贪心选大”,需在每轮优先执行该规则,缩小可选范围;
    • 吃货的决策是“基于馋嘴贪心的最优选择”,需通过DP/递归枚举所有可能并取最大值;
  3. 时间复杂度
    • 记忆化搜索:O(N²)(N<500,N²=25万,无性能压力);
    • DP:O(N³)(遍历首次选择O(N) + 每个线性序列DP O(N²)),仍满足题目限制。
四、核心结论

本题的核心是**“环形拆线性”+“博弈型DP/记忆化搜索”**:

  • 先拆解环形结构(遍历首次选择),将问题转化为线性缺口的博弈;
  • 再利用DP/记忆化搜索,结合“馋嘴贪心选大”的规则,枚举吃货的所有可选决策,最大化总收益。

代码

// 记忆化搜索
function solution() {
    const N  = Number(readline());
    const list = [];
    for (let i = 0; i < N; i++) {
        list[i] = Number(readline());
    }
    const cache = Array.from({length: N}, () => Array(N).fill(-1));
    
    const getMaxSum = (l, r, t) => {
        if (t <= 1) return 0;
        l = (l + N) % N;
        r = r % N;

        if (list[l] > list[r]) {
            l = (l - 1 + N) % N;
        } else {
            r = (r + 1) % N;
        }

        if (cache[l][r] !== -1) {
            return cache[l][r];
        }

        const s1 = list[l] + getMaxSum(l - 1, r, t - 2);
        const s2 = list[r] + getMaxSum(l, r + 1, t - 2);

        cache[l][r] = Math.max(s1, s2);
        return cache[l][r];
    };


    let maxSum = 0;
    for (let i = 0; i < N; i++) {
        const current = list[i] + getMaxSum(i-1, i+1, N-1);
        if (current > maxSum) {
            maxSum = current;
        }
    }
    console.log(maxSum);
}

// dp
function solution2() {
    const N  = Number(readline());
    const list = [];
    for (let i = 0; i < N; i++) {
        list[i] = Number(readline());
    }
    
    let maxSum = 0;
    
    // 遍历第一次选择的每一种可能(环形拆分为线性)
    for (let i = 0; i < N; i++) {
        // 第一次选择i后,剩余序列为 i+1 ~ n-1 拼接 0 ~ i-1(线性)
        const sub = list.slice(i+1).concat(list.slice(0, i));
        const m = sub.length; // m = n-1(奇数-1=偶数)

        // 初始化dp table,dp[l][r]表示子序列sub[l..r]的最大收益
        const dp = Array.from({ length: m }, () => Array(m).fill(0));

        // 填充dp(从长度为1的子序列开始)
        for (let len = 1; len <= m; len++) {
            for (let l = 0; l + len - 1 < m; l++) {
                const r = l + len - 1;
                if (l === r) {
                    // 只剩一块时,当前选择者直接拿走
                    dp[l][r] = sub[l];
                } else {
                    // 判断当前轮次:总长度m为偶数,len为当前子序列长度
                    // 初始总步数为m步(偶数),每选一次减少1步,步数奇偶性决定轮到谁
                    const steps = m - (r - l); // 剩余步数 = 总步数 - 已选步数
                    if (steps % 2 === 0) {
                        // 偶数步:轮到吃货(自己)选择,选两端中收益最大的
                        const takeLeft = sub[l] + dp[l + 1][r];
                        const takeRight = sub[r] + dp[l][r - 1];
                        dp[l][r] = Math.max(takeLeft, takeRight);
                    } else {
                        // 奇数步:轮到馋嘴选择,他会选两端中更大的,剩余给吃货
                        if (sub[l] >= sub[r]) {
                            // 馋嘴选左,吃货只能从l+1..r中选
                            dp[l][r] = dp[l + 1][r];
                        } else {
                            // 馋嘴选右,吃货只能从l..r-1中选
                            dp[l][r] = dp[l][r - 1];
                        }
                    }
                }
            }
        }

        // 第一次选择i的总收益 = arr[i] + 剩余序列的最大收益
        const total = list[i] + dp[0][m - 1];
        if (total > maxSum) {
            maxSum = total;
        }
    }

    console.log(maxSum);    
}


cases = [
    `5
8
2
10
5
7`,
`3
5
10
3`,
    `7
1
3
5
7
9
11
13`
];

let caseIndex = 0;
let lineIndex = 0;

const readline = (function () {
  let lines = [];
  return function () {
    if (lineIndex === 0) {
      lines = cases[caseIndex]
        .trim()
        .split("\n")
        .map((line) => line.trim());
    }
    return lines[lineIndex++];
  };
})();

cases.forEach((_, i) => {
  caseIndex = i;
  lineIndex = 0;
  solution2();
  console.log('-------');
});
华为od试题c要求实现数据类功能,通过输入一组数据,将其类为奇数、偶数、负数和零四种类型,并统计每种类型的数量。这是一个基础的数据处理问题,需要使用循环、判断等语句来实现。 首先需要输入一组数据,可以使用Scanner类实现。然后通过遍历数据数组,判断每个数的类型,使用if语句判断是否为奇数、偶数、负数或零,并统计每种类型的数量。最后输出每种类型的数量即可。 在实现过程中,需要注意一些细节,如输入数据的个数,数组的长度要足够存储所有输入数据;对负数的判断需要使用小于号而非等于号;输出时需要注意格式,可以使用printf方法进行格式化输出。 以下是代码实现示例: import java.util.Scanner; public class DataClassification { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); int[] nums = new int[n]; for (int i = 0; i < n; i++) { nums[i] = scanner.nextInt(); } int oddCount = 0; int evenCount = 0; int negativeCount = 0; int zeroCount = 0; for (int i = 0; i < n; i++) { if (nums[i] % 2 != 0) { oddCount++; } else { evenCount++; } if (nums[i] < 0) { negativeCount++; } if (nums[i] == 0) { zeroCount++; } } System.out.printf("奇数:%d个\n", oddCount); System.out.printf("偶数:%d个\n", evenCount); System.out.printf("负数:%d个\n", negativeCount); System.out.printf("零:%d个\n", zeroCount); } }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值