题解:洛谷 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
【核心思想】
-
问题分析:给定 \(n\) 头奶牛,每头奶牛距离牛圈 \(T_i\) 分钟,每分钟吃掉 \(D_i\) 朵花。FJ 每次只能赶一头奶牛回去,来回用时 \(2 \times T_i\) 分钟。在此期间,其他奶牛继续吃花。求最小化的总吃花数量。这是一个自定义排序贪心问题,关键在于确定最优的赶牛顺序。
-
算法选择:
- 排序规则推导:对于两头奶牛 \(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}\) 从小到大排序,比值小的先赶
- 后缀和优化:计算完成时间的后缀和,快速计算总延迟代价
- 排序规则推导:对于两头奶牛 \(i\) 和 \(j\),假设先赶 \(i\) 再赶 \(j\)
-
关键步骤:
- 读取输入:\(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),主要是排序复杂度
- 空间复杂度:\(O(n)\),存储奶牛信息和后缀和数组
-
自定义排序贪心的核心思想:
- 交换论证:通过比较相邻两个任务交换位置后的代价,推导出最优排序规则
- 排序规则:按 \(\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
浙公网安备 33010602011771号