题解:洛谷 P8092 Drought
【题目来源】
洛谷:P8092 [USACO22JAN] Drought B - 洛谷
【题目描述】
Farmer John 的草地里的草在一场大旱中都干死了。经过数小时的绝望和沉思,Farmer John 想到了一个绝妙的主意,购买玉米来喂养他宝贵的奶牛。
FJ 的\(N\)头奶牛\((1\le N\le 10^5)\)排成一行,队伍中的第 \(i\) 头奶牛的饥饿度为 \(h_i(0\le h_i\le 10^9)\)。由于奶牛是社会性动物,她们坚持一起进食,FJ 降低奶牛饥饿度的唯一方法是选择两头相邻的奶牛 \(i\) 和\(i+1\)并分别喂她们一袋玉米,令她们的饥饿度各减少\(1\)。
FJ 想将他的奶牛喂至所有的奶牛都具有相同的非负饥饿度。请帮助 FJ 求出他喂奶牛达到上述状态所需的最少玉米袋数,或者如果不可能达到,输出\(-1\)。
【输入】
每个测试用例包含多个独立的子测试用例,必须全部回答正确才能通过整个测试用例。
输入的第一行包含 \((1\le T \le 100)\),为你需要求解的子测试用例的数量。
以下是 \(T\) 个子测试用例,每个子测试用例包含两行。
第一行包含 \(N\),第二行包含 \(h_1,h_2,\dots,h_N\)。输入保证所有子测试用例的 \(N\) 之和不超过 \(10^5\)。
每个子测试用例的 \(N\) 的值可能不同。
【输出】
输出 \(T\) 行,每个测试用例输出一行。
【输入样例】
5
3
8 10 5
6
4 6 4 4 6 4
3
0 1 0
2
1 2
3
10 9 9
【输出样例】
14
16
-1
-1
-1
【核心思想】
-
问题分析:给定 \(N\) 头奶牛的饥饿度 \(h_1, h_2, \dots, h_N\),每次操作选择相邻两头奶牛各减 \(1\)。目标是使所有奶牛饥饿度相等且非负,求最小操作次数或判断不可能。本质是一个相邻配对操作的贪心调整 + 可行性判定问题。
-
算法选择:
- 贪心从左到右调整 + 多条件可行性检查:从左到右贪心处理,将相邻差值通过操作"传递"到右侧,最后检查剩余数组是否满足可均一化条件
-
关键步骤:
- 特判边界:
- \(N = 1\):无需操作,输出 \(0\)
- \(N = 2\):若 \(h_1 \neq h_2\) 则不可能(输出 \(-1\)),否则输出 \(0\)
- 贪心从左到右消差(\(i\) 从 \(2\) 到 \(N\)):
- 若 \(a[i] \geq a[i-1]\):计算差值 \(\text{delta} = a[i] - a[i-1]\)
- \(a[i] \mathrel{-}= \text{delta}\),\(a[i+1] \mathrel{-}= \text{delta}\)(将差值传递给下一头奶牛)
- \(\text{ans} \mathrel{+}= 2 \cdot \text{delta}\)(每次操作两头各减 \(1\),需要 \(\text{delta}\) 次操作,共消耗 \(2 \cdot \text{delta}\) 袋玉米)
- \(i++\)(跳过已处理的下一头)
- 若 \(a[i] \geq a[i-1]\):计算差值 \(\text{delta} = a[i] - a[i-1]\)
- 可行性检查:
- 负值检查:排序后若有 \(a[i] < 0\),输出 \(-1\)
- 单调递增检查:若数组严格递增(\(a[i] > a[i-1]\) 对所有 \(i\)),输出 \(-1\)
- 非最小值数量奇偶性:找到第一个不等于最小值的位置 \(\text{pos}\),若 \((N - (\text{pos}-1))\) 为奇数,输出 \(-1\)
- 总和奇偶性:计算 \(\text{ans} = \sum_{i=2}^{N} (a[i] - a[1])\),若为奇数输出 \(-1\)
- 输出答案:通过所有检查后输出 \(\text{ans}\)
- 特判边界:
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),贪心调整 \(O(N)\),排序 \(O(N \log N)\)
- 空间复杂度:\(O(N)\),存储饥饿度数组
-
贪心传递差值 + 可行性判定的核心思想:
- 差值向右传递:当 \(a[i] > a[i-1]\) 时,必须通过操作将 \(a[i]\) 降到与 \(a[i-1]\) 相同,多余的差值只能传递给 \(a[i+1]\)(因为每次操作必须同时减相邻两头)
- 操作代价计算:每次操作消耗 \(2\) 袋玉米(两头各 \(1\) 袋),差值 \(\text{delta}\) 需要 \(\text{delta}\) 次操作,故增加 \(2 \cdot \text{delta}\)
- 奇偶性约束:由于每次操作同时影响两头奶牛,总饥饿度减少量为偶数,因此最终可均一化的必要条件是总减少量为偶数
- 非最小值数量的奇偶性:最终所有奶牛要降到同一水平,非最小值部分需要成对操作,若数量为奇数则无法完全配对
- 适用于相邻配对操作、差值传递、资源均衡化类问题
【解题思路】

【算法标签】
普及 #其他排序
【代码详解】
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t, n, a[100005], b[100005];
int main()
{
cin >> t; // 输入t
while (t--) { // 遍历t次循环
ll ans=0; // 定义最少玉米袋数,需使用long long类型
cin >> n; // 输入n
for (int i=1; i<=n; i++) { // 记录n头奶牛的饥饿度
cin >> a[i];
}
if (n==1) { // 特判:如果n为1
cout << 0 << endl; // 输出0,因为此时无需调整
continue;
}
if (n==2 && a[1]!=a[2]) { // 特判:如果n为2,且两头牛饥饿度不一样
cout << -1 << endl; // 输出-1,因为无法调整
continue;
} else if (n==2 && a[1]==a[2]) { // 如果n为2,且两头牛饥饿度一样
cout << 0 << endl; // 输出0,因为无需调整
continue;
}
for (int i=2; i<=n; i++) { // 从第2头牛开始遍历
if (a[i]>=a[i-1]) { // 只要当前牛的饥饿度大于前一头牛
int delta = a[i]-a[i-1]; // 计算两个之间的差值
a[i+1] = a[i+1] - delta; // 下一头牛需要减去这个差值
a[i] = a[i] - delta; // 当前的牛也把饥饿度变的和前一头一样
ans += 2*delta; // ans增加2个差值
i = i+1; // 下次循环i从i+2开始(但这里需设置为i+1,因为还有i++)
}
}
sort(a+1, a+n+1); // 一轮遍历完后进行从小到大的排序
int mark=0; // 定义标记位,准备特判
for (int i=1; i<=n; i++) { // 遍历n头牛
if (a[i]<0) { // 如果其中有小于0的
mark=1; // 则修改mark为1
break; // 退出循环
}
}
if (mark==1) { // 对于一轮遍历调整后小于0的,不符合题意
cout << -1 << endl; // 输出-1
continue;
}
int cnt = 0; // 定义标记位,准备特判
for (int i=2; i<=n; i++) { // 遍历n头牛
if (a[i]>a[i-1]) cnt++; // 如果属于为连续递增
}
if (cnt==n-1) { // 则cnt为n-1
cout << -1 << endl; // 输出-1,因为连续递增的场景无解
continue;
}
int pos = 0; // 定义标记位,准备特判
for (int i=1; i<=n; i++) { // 遍历n头牛
if (a[i]!=a[1]) { // 找到第1头和最小值不同的位置
pos = i;
break;
}
}
if ((n-(pos-1))%2==1) { // 如果除最小值外的牛的数量是奇数,也无法调整
cout << -1 << endl;
continue;
}
for (int i=2; i<=n; i++) { // 遍历n头牛
ans += a[i]-a[1]; // 计算每两头牛的差值,并累加,这个就是要减少的饥饿度总和
}
if (ans%2==1) { // 如果总和是奇数
cout << -1 << endl; // 也是无解的
continue;
} else { // 只有总和是偶数时,总和还是最终的解
cout << ans << endl;
}
}
return 0;
}
【运行结果】
5
3
8 10 5
14
6
4 6 4 4 6 4
16
3
0 1 0
-1
2
1 2
-1
3
10 9 9
-1
浙公网安备 33010602011771号