题解:洛谷 P3029 Cow Lineup

【题目来源】

洛谷:P3029 [USACO11NOV] Cow Lineup S - 洛谷

【题目描述】

农民约翰雇一个专业摄影师给他的部分牛拍照。由于约翰的牛有好多品种,他喜欢他的照片包含每个品种的至少一头牛。

约翰的牛都站在一条沿线的不同地方,每一头牛由一个整数位置 \(X_i\) 以及整数品种编号 \(ID_i\) 表示。

约翰想拍一张照片,这照片由沿线的奶牛的连续范围组成。照片的成本与规模相当,这就意味着,在一系列照片中的最大和最小 \(X\) 坐标的差距决定了照片的成本。

请帮助约翰计算最小的照片成本,这些照片中有每个不同的品种的至少一头牛,没有两头牛愿意站在同一个地点的。

【输入】

\(1\) 行:牛的数量 \(N\)

\(2..1+N\) 行:每行包含 2 个以空格分隔的正整数 \(X_i\)\(ID_i\);意义如题目描述;

【输出】

输出共一行,包含每个不同品种 \(ID\) 的照片的最低成本。

【输入样例】

6
25 7
26 1
15 1
22 3
20 1
30 1

【输出样例】

4

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛的位置 \(X_i\) 和品种 \(ID_i\),求包含所有不同品种的连续子数组的最小长度(位置差)。本质上是滑动窗口(双指针)问题:排序后维护一个窗口,使其包含所有品种,然后收缩左边界求最小长度。

  2. 算法选择

    • 排序:按位置 \(X_i\) 升序排序
    • 滑动窗口:右指针扩展窗口,左指针收缩窗口,用哈希表统计窗口内各品种数量
  3. 关键步骤

    • 读取数据:读入 \(N\)\(N\) 头奶牛的信息,用 map 统计不同品种数 \(m\)
    • 排序:按位置升序排序
    • 滑动窗口\(i = 0\)\(N-1\),右指针):
      • s[a[i].type]++
      • s[a[i].type] == 2x++(该品种出现第二次,说明窗口内已有该品种)
      • 收缩左边界(当 x == m):
        • \(ans = \min(ans, a[i].pos - a[head].pos)\)
        • s[a[head].type]--
        • s[a[head].type] == 1x--
        • head++
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),排序主导
    • 空间复杂度:\(O(N)\),哈希表存储
  5. 滑动窗口的核心思想

    • 排序后的连续性:排序后,包含所有品种的最小区间一定是连续子数组,可用双指针维护
    • 品种覆盖判定x 记录窗口中"出现次数 \(\geq 2\)"的品种数,当 x == m 时说明所有品种至少出现一次(因为每个品种首次出现时计数为 \(1\),第二次出现时 x 才增加)
    • 左边界收缩:当窗口满足条件时,尝试右移左边界以缩小窗口,同时更新最小长度
    • 哈希表的动态维护s[type] 记录当前窗口中该品种的数量,支持 \(O(1)\) 的增减和查询
    • 适用于区间覆盖、最小窗口、双指针类问题

【解题思路】

【算法标签】

普及 #双指针

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// 奶牛结构体,存储位置和品种
struct cow
{
    int pos, type;
} a[50005];  // 存储所有奶牛信息

int n, m, head, x, ans = 2e9;  // n: 奶牛数量, m: 品种数, head: 滑动窗口左边界
                                // x: 当前窗口包含的品种数, ans: 最小覆盖区间长度
map<int, int> s;  // 记录各品种在窗口中的出现次数

// 比较函数:按位置升序排序
bool cmp(cow x, cow y)
{
    return x.pos < y.pos;
}

int main()
{
    // 输入奶牛数量
    cin >> n;

    // 输入每头奶牛的位置和品种,并记录品种
    for (int i = 0; i < n; i++)
    {
        cin >> a[i].pos >> a[i].type;
        s[a[i].type] = 1;  // 标记该品种存在
    }

    m = s.size();  // 计算不同品种的总数

    // 按位置排序奶牛
    sort(a, a + n, cmp);

    // 滑动窗口算法寻找最小覆盖区间
    for (int i = 0; i < n; i++)
    {
        s[a[i].type]++;  // 当前品种计数增加

        // 如果该品种首次在窗口中出现两次,增加x
        if (s[a[i].type] == 2)
        {
            x++;
        }

        // 当窗口包含所有品种时,尝试收缩左边界
        while (x == m)
        {
            // 更新最小区间长度
            ans = min(a[i].pos - a[head].pos, ans);

            // 左边界移动,对应品种计数减少
            s[a[head].type]--;

            // 如果某个品种不再满足覆盖条件,减少x
            if (s[a[head].type] == 1)
            {
                x--;
            }
            head++;  // 移动左边界
        }
    }

    // 输出结果
    cout << ans;

    return 0;
}

【运行结果】

6
25 7
26 1
15 1
22 3
20 1
30 1
4
posted @ 2026-08-17 09:47  团爸讲算法  阅读(9)  评论(0)    收藏  举报