题解:洛谷 P5541 Sleepy Cow Herding

【题目来源】

洛谷:P5541 [USACO19FEB] Sleepy Cow Herding S - 洛谷

【题目描述】

Farmer John的\(N\)头奶牛,总是会迷路走到农场上遥远的地方去!他需要你帮助将她们一起赶回来。农场的草地大体是一块狭长的区域——我们可以将其想象成一条数轴,奶牛可以占据数轴上的任意整数位置。这\(N\)头奶牛现在正位于不同的整数位置,Farmer John想要移动她们,使得她们占据\(N\)个相邻的位置(例如,位置\(6\)\(7\)\(8\))。

不幸的是,奶牛们现在很困,Farmer John要让她们集中精力听从命令移动并不容易。任意时刻,他只能使得一头处在“端点”(在所有奶牛中位置最小或最大)位置的奶牛移动。当他移动奶牛时,他可以命令她走到任意一个未被占用的整数位置,只要在新的位置上她不再是一个端点。可以看到随着时间的推移,这样的移动可以使奶牛们趋向越来越近。

请求出使得奶牛们集中到相邻位置所进行的移动次数的最小和最大可能值。

【输入】

先输入一个整数\(N(N\le 100000)\),接下来输入\(N\)个数,表示N头奶牛的位置。

【输出】

输出的第一行包含Farmer John需要将奶牛们聚集起来所需进行的最小移动次数。第二行包含他将奶牛聚集起来能够进行的最大移动次数。

【输入样例】

3
4
7
9

【输出样例】

1
2

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛在数轴上的不同整数位置,每次只能移动端点(最小或最大位置)的奶牛到任意非端点空位。目标是让所有奶牛占据 \(N\) 个相邻位置,求最小移动次数最大移动次数

  2. 算法选择

    • 双指针求最小值:排序后,用双指针找到最长的"可压缩区间"——即区间长度 \(\leq N\) 的连续子序列,最小移动次数为 \(N\) 减去该区间内奶牛数量
    • 贪心求最大值:每次移动端点奶牛到最远可行位置,最大化移动步数,等价于计算总空位数减去最优压缩后的剩余空位
  3. 关键步骤

    • 排序:将奶牛位置 \(a[1..N]\) 按升序排列
    • 最小值计算
      • 特判边界情况:若 \(n-1\) 头奶牛已相邻(\(a[n-1] - a[1] = N-2\)\(a[n] - a[2] = N-2\)),且剩余一头与这群距离 \(> 2\),则最小移动次数为 \(2\)(需要两步将孤立奶牛"拉"入群体)
      • 双指针扫描:初始化指针 \(i = 1, j = 1\),对于每个左端点 \(i\)
        • 右移 \(j\) 直到 \(a[j+1] - a[i] + 1 > N\)(区间长度超过 \(N\) 则停止)
        • 记录区间 \([i, j]\) 内奶牛数量 \(j - i + 1\),更新最大值 \(\text{minn} = \max(\text{minn}, j - i + 1)\)
        • 最小移动次数为 \(N - \text{minn}\)(将区间外的奶牛移入该区间)
    • 最大值计算\(\text{maxn} = \max(a[n] - a[2], a[n-1] - a[1]) - N + 2\),即两端空位总数减去内部已压缩部分
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),排序 \(O(N \log N)\),双指针扫描 \(O(N)\)
    • 空间复杂度:\(O(N)\),存储位置数组
  5. 双指针 + 端点移动的核心思想

    • 区间压缩视角:将问题转化为寻找一个长度 \(\leq N\) 的区间,使得区间内已有奶牛最多,区间外奶牛需要被移入
    • 端点移动的约束:只能移动端点意味着每次移动都会缩小当前位置范围,双指针找到的区间即为最优"目标聚集区"
    • 特判的物理意义:当 \(n-1\) 头奶牛已聚集成相邻块,而第 \(n\) 头孤立在远处时,由于端点移动规则限制,孤立奶牛无法一步跳入聚集块,需要 \(2\) 步(先移到块旁边,再被"挤"入)
    • 最大值的贪心本质:每次移动将端点奶牛移到尽可能远的位置(但仍保持非端点),最大化每次移动创造的空位,最终总空位数即为最大移动次数
    • 适用于数轴上区间压缩、端点移动约束的聚集问题

【解题思路】

【算法标签】

普及 #双指针

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, a[100005], minn=-1e9;
int main()
{
    cin >> n;  // 输入n
    for (int i=1; i<=n; i++) {  // 输入n头奶牛的位置
        cin >> a[i];
    }
    sort(a+1, a+n+1);  // 按照从小到大排序
    // 先判断最小值
    if (a[n-1]-a[1]==n-2 && a[n]-a[n-1]>2) minn = 2;  // 特判场景1:n-1头牛挤在左边,最后1头在右边,此时最小移动步数为2
    else if (a[n]-a[2]==n-2 && a[2]-a[1]>2) minn = 2;  // 特判场景2:n-1头牛挤在右边,最后1头在左边,此时最小移动步数为2
    else {  // 其他场景
        for (int i=1, j=1; i<=n; i++) {  // 使用双指针,遍历n头牛
            while (j<n && a[j+1]-a[i]+1<=n) {  // 需要找到j,满足a[j]-a[i]+1==n。如果a[j]-a[i]+1不是刚好等于n,而是a[j]-a[i]+1<n,a[j+1]-a[i]+1>n,移动n-(j-i+1)头奶牛
                j++;
            }
            minn = max(minn, j-i+1);  // i到j区间内牛的最大数量j-i+1
        }
        minn = n - minn;  // 再用n减去牛的数量,就是移动的最小数量
    }
    cout << minn << endl;
    int maxn = max(a[n]-a[2], a[n-1]-a[1]) - n + 2;  // (a[n]-a[1]+1) - (n-1),即所有位置的数量-牛的数量得到空位的数量
    cout << maxn << endl;
    return 0;
}

【运行结果】

3
4
7
9
1
2
posted @ 2026-08-29 09:38  团爸讲算法  阅读(2)  评论(0)    收藏  举报