cn_xpr

✨ 欢迎来到我的OI博客!

P2985 [USACO10FEB] Chocolate Eating S 题解 二分答案

P2985 [USACO10FEB] Chocolate Eating S 题解

题目理解

首先,我们需要清楚地理解题目要求:

  • 我们有\(N\)块巧克力,每块有一个特定的快乐值\(H_i\).
  • 需要在\(D\)天内吃完这些巧克力
  • 每天可以选择吃任意数量的巧克力(可以不吃,也可以全吃)
  • 每天获得的快乐值是当天吃的所有巧克力的快乐值之和
  • 需要最大化这\(D\)天中最小的每日快乐值(即最大化"最小日快乐")

解题思路

这个问题可以使用二分答案的方法来解决。具体思路如下:

  1. 二分搜索最小日快乐值:我们可以对可能的最小日快乐值进行二分搜索。对于每一个候选值,我们检查是否可以在\(D\)天内分配巧克力,使得每天的快乐值都不小于这个候选值。

  2. 检查可行性:对于给定的候选值\(mid\),我们需要判断是否存在一种分配方式满足条件。具体方法是贪心地分配巧克力,尽可能让每天的快乐值达到\(mid\),同时不超过D天。

算法步骤

  1. 确定二分范围:最小可能值是0,最大可能值是所有巧克力快乐值之和。
  2. 进行二分搜索:
    • mid = (left + right + 1) / 2
    • 检查mid是否可行
    • 如果可行,则尝试更大的值(left = mid)
    • 否则尝试更小的值(right = mid - 1)
  3. 检查可行性的方法:
    • 从第一天开始,尽可能多吃巧克力直到当天快乐值\(≥mid\)
    • 记录需要的天数
    • 如果总天数\(≤D\),则可行

代码实现

#include <iostream>
#include <vector>
using namespace std;

typedef long long ll;

int N, D;
vector<ll> H;

// 检查是否可以在D天内分配巧克力使得每天快乐值都不小于min_happy
bool check(ll min_happy)
{
    ll cur = 0;
    int days = 0;

    for (int i = 0; i < N;)
    {
        while (i < N && cur < min_happy)
        {
            cur += H[i];
            i++;
        }
        if (cur >= min_happy)
        {
            days++;
            cur = 0;
        }
        if (days >= D)
            return true;
    }

    return days >= D;
}

// 重建分配方案
vector<int> solve(ll min_happy)
{
    vector<int> res(N, D); // 默认最后一天吃
    ll cur = 0;
    int day = 1;

    for (int i = 0; i < N;)
    {
        int start = i;
        while (i < N && cur < min_happy)
        {
            cur += H[i];
            i++;
        }
        for (int j = start; j < i; j++)
        {
            res[j] = day;
        }
        if (cur >= min_happy)
        {
            day++;
            cur = 0;
        }
        if (day > D)
            break;
    }

    return res;
}

int main()
{
    cin >> N >> D;
    H.resize(N);
    ll sum = 0;

    for (int i = 0; i < N; i++)
    {
        cin >> H[i];
        sum += H[i];
    }

    ll left = 0, right = sum;
    ll best = 0;

    // 二分查找最大可能的最小日快乐值
    while (left <= right)
    {
        ll mid = (left + right) / 2;
        if (check(mid))
        {
            best = mid;
            left = mid + 1;
        }
        else
        {
            right = mid - 1;
        }
    }

    cout << best << endl;

    // 重建分配方案
    vector<int> ans = solve(best);
    for (int day : ans)
    {
        cout << day << endl;
    }

    return 0;
}

代码解释

  1. check函数:检查给定的\(min\) \(happy\)是否可行。($min_happy$会变成\(min_happy\))通过贪心地分配巧克力,计算需要多少天才能满足每天快乐值\(≥min\) \(happy\)

  2. solve函数:在找到最大\(min\) \(happy\)后,重建具体的分配方案。记录每块巧克力在哪一天被吃掉。

  3. main函数

    • 读取输入数据
    • 进行二分搜索找到最大可能的最小日快乐值
    • 输出结果和分配方案

复杂度分析

  • 时间复杂度:\(O(N log(sum(H_i)))\),其中\(sum(H_i)\)是所有巧克力快乐值的和。二分搜索需要\(O(log(sum(H_i)))\)次迭代,每次check需要\(O(N)\)时间。
  • 空间复杂度:\(O(N)\),用于存储巧克力快乐值和分配方案。

注意事项

  1. 二分搜索的边界条件要正确处理,避免无限循环。
  2. 在重建分配方案时,要注意处理最后几天可能不需要吃满的情况。
  3. 题目要求输出方案时,如果有多解,输出最早达到最小快乐值的方案,我们的贪心方法自然满足这一条件。

25-8-8 补

朴素做法(非二分答案方法)题解

对于P2985这道题,除了二分答案的优化方法外,我们也可以考虑一种更直观的朴素做法。虽然时间复杂度较高,但对于理解问题本质很有帮助。

朴素思路

  1. 直接枚举最小日快乐值:从可能的最大值开始向下枚举,直到找到第一个可行的值。
  2. 验证每个候选值:对于每个候选的最小日快乐值,模拟分配过程,检查是否能在D天内完成分配。
  3. 记录最优解:一旦找到可行的分配方案,立即记录并输出结果。

算法步骤

  1. 计算所有巧克力快乐值总和sum
  2. 从sum/D开始向下枚举可能的最小日快乐值H
  3. 对于每个H:
    • 模拟分配过程:每天尽可能多吃巧克力直到当天快乐值≥H
    • 如果能在D天内分配完所有巧克力且满足每日≥H,则H就是解
  4. 输出最大的满足条件的H及其分配方案

代码实现

#include <iostream>
#include <vector>
using namespace std;

typedef long long ll;

int N, D;
vector<ll> H;

// 检查给定min_happy是否可行,并返回分配方案
pair<bool, vector<int>> check_slow(ll min_happy) 
{
    vector<int> res(N, D); // 默认最后一天吃
    ll cur = 0;
    int day = 1;
    int i = 0;
    
    while (i < N && day <= D) 
    {
        int start = i;
        while (i < N && cur < min_happy) 
        {
            cur += H[i];
            i++;
        }
        for (int j = start; j < i; j++) 
        {
            res[j] = day;
        }
        if (cur >= min_happy) 
        {
            day++;
            cur = 0;
        }
    }
    
    bool ok = (i == N) && (day <= D + 1);
    return {ok, res};
}

int main() 
{
    cin >> N >> D;
    H.resize(N);
    ll sum = 0;
    
    for (int i = 0; i < N; i++) 
    {
        cin >> H[i];
        sum += H[i];
    }
    
    ll max_possible = sum / D;
    ll best_h = 0;
    vector<int> best_schedule;
    
    // 从大到小枚举可能的最小日快乐值
    for (ll h = max_possible; h >= 0; h--) 
    {
        auto [ok, res] = check_slow(h);
        if (ok) {
            best_h = h;
            best_schedule = res;
            break; // 找到最大的可行h即可停止
        }
    }
    
    cout << best_h << endl;
    for (int day : best_schedule) 
    {
        cout << day << endl;
    }
    
    return 0;
}

代码解释

  1. check_slow函数

    • 尝试按照给定的\(min_happy\)分配巧克力
    • 返回是否可行以及具体的分配方案
    • 使用贪心方法,每天尽可能多吃直到达到\(min_happy\)
  2. 主函数

    • 读取输入数据
    • 计算所有巧克力快乐值总和
    • 从可能的最大值\(sum/D\)开始向下枚举
    • 对每个候选值检查可行性
    • 找到第一个可行的值即为解
    • 输出结果和分配方案

复杂度分析

  • 时间复杂度:\(O(sum/D × N)\),其中\(sum\)是所有巧克力快乐值的和。最坏情况下需要枚举\(sum/D\)次,每次检查需要\(O(N)\)时间。
  • 空间复杂度:\(O(N)\),用于存储巧克力快乐值和分配方案。

优缺点

优点

  • 思路直观,易于理解和实现
  • 不需要理解二分搜索等高级算法

缺点

  • \(sum\)很大时,枚举次数会非常多,效率低下
  • 对于大规模数据无法在合理时间内完成

优化方向

这个朴素方法虽然简单,但对于较大的数据集显然不够高效。可以在此基础上进行以下优化:

  1. 从合理范围开始枚举:不必从0开始,可以从\(sum/D\)开始向下枚举
  2. 提前终止:一旦找到可行解立即终止,因为是从大到小枚举
  3. 转化为二分搜索:这正是我们之前给出的优化方法,将时间复杂度从\(O(sum/D × N)\)降为\(O(N log(sum))\)

qwq给个赞+关注吧QAQ

posted @ 2025-08-17 16:22  cn_xpr  阅读(15)  评论(0)    收藏  举报