题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛的饥饿度 \(h_1, h_2, \dots, h_N\),每次操作选择相邻两头奶牛各减 \(1\)。目标是使所有奶牛饥饿度相等且非负,求最小操作次数或判断不可能。本质是一个相邻配对操作的贪心调整 + 可行性判定问题。

  2. 算法选择

    • 贪心从左到右调整 + 多条件可行性检查:从左到右贪心处理,将相邻差值通过操作"传递"到右侧,最后检查剩余数组是否满足可均一化条件
  3. 关键步骤

    • 特判边界
      • \(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] < 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}\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),贪心调整 \(O(N)\),排序 \(O(N \log N)\)
    • 空间复杂度:\(O(N)\),存储饥饿度数组
  5. 贪心传递差值 + 可行性判定的核心思想

    • 差值向右传递:当 \(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
posted @ 2026-08-29 09:40  团爸讲算法  阅读(7)  评论(0)    收藏  举报