题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 头奶牛的位置 \(X_i\) 和品种 \(ID_i\),求包含所有不同品种的连续子数组的最小长度(位置差)。本质上是滑动窗口(双指针)问题:排序后维护一个窗口,使其包含所有品种,然后收缩左边界求最小长度。
-
算法选择:
- 排序:按位置 \(X_i\) 升序排序
- 滑动窗口:右指针扩展窗口,左指针收缩窗口,用哈希表统计窗口内各品种数量
-
关键步骤:
- 读取数据:读入 \(N\) 和 \(N\) 头奶牛的信息,用
map统计不同品种数 \(m\) - 排序:按位置升序排序
- 滑动窗口(\(i = 0\) 到 \(N-1\),右指针):
s[a[i].type]++- 若
s[a[i].type] == 2,x++(该品种出现第二次,说明窗口内已有该品种) - 收缩左边界(当
x == m):- \(ans = \min(ans, a[i].pos - a[head].pos)\)
s[a[head].type]--- 若
s[a[head].type] == 1,x-- head++
- 输出 \(ans\)
- 读取数据:读入 \(N\) 和 \(N\) 头奶牛的信息,用
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),排序主导
- 空间复杂度:\(O(N)\),哈希表存储
-
滑动窗口的核心思想:
- 排序后的连续性:排序后,包含所有品种的最小区间一定是连续子数组,可用双指针维护
- 品种覆盖判定:
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
浙公网安备 33010602011771号