题解:洛谷 P2878 Protecting the Flowers

【题目来源】

洛谷:P2878 [USACO07JAN] Protecting the Flowers S - 洛谷

【题目描述】

\(n\) 头奶牛跑到 FJ 的花园里去吃花儿了,它们分别在距离牛圈 \(T_i\)(这里指 FJ 到那里需要 \(T_i\) 分钟)处吃花,每分钟会吃掉 \(D_i\) 朵花,FJ 现在要将它们给弄回牛圈,但是他每次只能弄一头回去,来回用时总共为 \(2 \times T_i\) 分钟,在这段时间内,其它的奶牛会继续吃 FJ 的花,速度保持不变,当然正在被赶回牛圈的奶牛不能继续吃了。现在求在最好的方案下奶牛吃掉花的最小朵数。

【输入】

第一行一个正整数 \(n\)

下面 \(n\) 行,每行两个正整数 \(T_i,D_i\)

【输出】

一行一个整数表示答案。

【输入样例】

6
3 1
2 5
2 3
3 2
4 1
1 6

【输出样例】

86

【核心思想】

  1. 问题分析:给定 \(n\) 头奶牛,每头奶牛距离牛圈 \(T_i\) 分钟,每分钟吃掉 \(D_i\) 朵花。FJ 每次只能赶一头奶牛回去,来回用时 \(2 \times T_i\) 分钟。在此期间,其他奶牛继续吃花。求最小化的总吃花数量。这是一个自定义排序贪心问题,关键在于确定最优的赶牛顺序。

  2. 算法选择

    • 排序规则推导:对于两头奶牛 \(i\)\(j\),假设先赶 \(i\) 再赶 \(j\)
      • 先赶 \(i\) 的代价:\(2 \times T_i \times D_j\)\(i\) 被赶期间 \(j\) 吃的花)
      • 先赶 \(j\) 的代价:\(2 \times T_j \times D_i\)\(j\) 被赶期间 \(i\) 吃的花)
      • \(T_i \times D_j < T_j \times D_i\),即 \(\frac{T_i}{D_i} < \frac{T_j}{D_j}\),则先赶 \(i\) 更优
    • 排序策略:按 \(\frac{T_i}{D_i}\) 从小到大排序,比值小的先赶
    • 后缀和优化:计算完成时间的后缀和,快速计算总延迟代价
  3. 关键步骤

    • 读取输入\(n\)(奶牛数量)、\(T_i\)(距离)、\(D_i\)(吃花速度)
    • 自定义排序:按 \(T_i \times D_j > T_j \times D_i\)(即 \(\frac{T_i}{D_i} > \frac{T_j}{D_j}\))排序
    • 计算后缀和(从后往前遍历):
      • sa[i] = sa[i+1] + a[i].t:计算从第 \(i\) 个任务开始到最后的总时间
    • 计算总代价(遍历 \(i\)\(1\)\(n\)):
      • ans += 2 * a[i].d * sa[i+1]:当前任务的延迟代价 = \(2 \times\) 吃花速度 \(\times\) 后面任务的总时间
    • 输出结果:最小总吃花数量 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),主要是排序复杂度
    • 空间复杂度:\(O(n)\),存储奶牛信息和后缀和数组
  5. 自定义排序贪心的核心思想

    • 交换论证:通过比较相邻两个任务交换位置后的代价,推导出最优排序规则
    • 排序规则:按 \(\frac{T}{D}\) 比值排序,比值小的任务优先执行,可以最小化总延迟代价
    • 后缀和优化:预处理后缀和,将总代价计算优化到 \(O(n)\)
    • 贪心正确性:自定义排序确保任意相邻两个任务的顺序都是最优的,从而整体顺序最优
    • 适用于任务调度、最小化总延迟代价、带权排序类问题

【算法标签】

普及 #贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005;
int n, sa[N], ans;  // n: 任务数, sa: 后缀和, ans: 答案
struct Node
{
    int t, d;  // t: 完成时间, d: 延迟代价权重
} a[N];

// 比较函数:按t[i]/d[i]从大到小排序
bool cmp(Node x, Node y)
{
    return x.t * y.d > x.d * y.t;  // 等价于x.t/x.d > y.t/y.d,避免浮点数
}
signed main()
{
    cin >> n;  // 输入任务数
    for (int i = 1; i <= n; i++)  // 输入每个任务的时间和延迟权重
    {
        cin >> a[i].t >> a[i].d;
    }
    sort(a + 1, a + n + 1, cmp);  // 按比值排序

    // 计算完成时间的后缀和
    for (int i = n; i >= 1; i--)
    {
        sa[i] = sa[i + 1] + a[i].t;
    }

    // 计算总延迟代价
    for (int i = 1; i <= n; i++)
    {
        ans += 2 * a[i].d * sa[i + 1];  // 公式:2×d[i]×后面任务的总时间
    }
    cout << ans << endl;  // 输出结果
    return 0;
}

【运行结果】

6
3 1
2 5
2 3
3 2
4 1
1 6
86
posted @ 2026-06-14 10:49  团爸讲算法  阅读(9)  评论(0)    收藏  举报