当前位置: 首页 > news >正文

Luogu P3029 [USACO11NOV] Cow Lineup S

题目分析

首先,我们来深入理解题目的要求。假设我们有 \(k\) 个不同的品种。我们需要找到一个 \(x\) 坐标的区间 \([x_{min}, x_{max}]\),这个区间内的所有奶牛,其品种集合包含了全部 \(k\) 个品种,并且我们希望 \(x_{max} - x_{min}\) 的值尽可能小。

直接处理无序的 \(x\) 坐标非常困难。如果我们想确定一个区间的最小和最大 \(x\) 坐标,我们最好先让这些坐标有序。因此,一个非常自然且关键的预处理步骤是:将所有奶牛按照它们的 \(x\) 坐标从小到大排序

排序后,问题就转化为了:在一个按 \(x\) 坐标排好序的奶牛数组 cows 中,找到一个连续的子数组 cows[i...j],这个子数组包含了所有品种的奶牛,并且 cows[j].x - cows[i].x 的值最小。这个问题就变成了一个经典的寻找满足特定条件的最短连续子数组的问题,非常适合使用双指针(滑动窗口)算法来解决。

算法思路:滑动窗口

滑动窗口算法是解决这类问题的绝佳工具。我们可以想象一个“窗口”在排好序的奶牛数组上滑动。这个窗口由两个指针定义:左指针 i 和右指针 j。窗口代表了我们当前考虑的奶牛子数组 cows[i...j]

我们的策略是:

  1. 扩展窗口:不断向右移动右指针 j,将新的奶牛纳入窗口,直到窗口内的奶牛满足“包含所有品种”的条件。
  2. 记录并收缩窗口:一旦条件满足,我们就找到了一个“可行解”。我们计算当前窗口的成本 (cows[j].x - cows[i].x),并用它来更新全局最小成本。然后,我们尝试让这个窗口变得更小以寻找更优解。方法是向右移动左指针 i,将奶牛 cows[i] 移出窗口。
  3. 重复:移出 cows[i] 后,窗口可能不再满足条件(比如 cows[i] 是窗口内最后一个某品种的奶牛)。这时,我们回到步骤1,继续向右移动 j,寻找下一个满足条件的窗口。

这个过程持续进行,直到两个指针都遍历完整个数组。因为两个指针都只从左到右单向移动,所以总的时间复杂度是线性的。我们需要以下的数据结构:

  • 品种计数器 (counts):一个哈希表(在 Python 中是 dictdefaultdict),用来记录当前窗口内每个品种的奶牛数量。例如 counts[breed_id] = 2 表示窗口内有2头 breed_id 品种的奶牛。
  • 已满足的品种数 (have):一个整数,记录当前窗口内包含了多少个不同的品种。
  • 总品种数 (total_type):一个整数,表示牛群中总共有多少个不同的品种。这是我们判断窗口是否满足条件的目标。

重要步骤:窗口移动

  1. 我们使用一个 for 循环来遍历左指针 i,从 0N-1。在每次循环中,i 代表了我们尝试收缩的窗口的左边界。
  2. for 循环内部,我们使用一个 while 循环来扩展右指针 j
    • while 循环的条件是 have < total_type 并且 j < N。这意味着只要我们还没找齐所有品种,并且右指针没有越界,就继续扩展。
    • while 循环中:
      • 获取右指针 j 指向的奶牛 cows[j] 的品种 p = cows[j][1]
      • 如果 counts[p] == 0,说明这是一种新加入窗口的品种,所以 have += 1
      • 然后,将该品种的计数加一:counts[p] += 1
      • 最后,右指针向右移动:j += 1
  3. while 循环结束时,有两种可能:
    • j 到达了数组末尾,但 have 仍然小于 total_type。这意味着从当前 i 开始,无法构成一个包含所有品种的有效窗口了。循环可以提前结束。
    • have == total_type。我们成功找到了一个从 i 开始的,包含所有品种的最短窗口。这个窗口是 cows[i...j-1](因为 j 在最后一次成功添加后又自增了1)。
  4. 如果 have == total_type
    • 计算成本:cost = cows[j-1][0] - cows[i][0]
    • 更新全局最小成本:ans = min(ans, cost)
  5. for 循环的末尾(准备下一次 i 的迭代前),我们需要将 cows[i] 移出窗口:
    • 获取左指针 i 指向的奶牛的品种 p_left = cows[i][1]
    • 将该品种的计数减一:counts[p_left] -= 1
    • 如果 counts[p_left] == 0,说明我们失去了窗口中该品种的最后一头牛,所以 have -= 1

代码解析

import sys
from collections import defaultdict# 1. 快速读入
# sys.stdin.read().strip().split() 一次性读取所有输入,分割成一个字符串列表
# 这种方式比逐行读取 input() 在数据量大时更快
data = sys.stdin.read().strip().split()
it = iter(data) # 创建一个迭代器,方便按顺序取数# 2. 预处理
n = int(next(it))
cows, seen_type, total_type = [], {}, 0
for _ in range(n):x = int(next(it))p = int(next(it))cows.append((x, p))# 使用字典 seen_type 来模拟集合,统计总品种数if not seen_type.get(p, False):seen_type[p] = Truetotal_type += 1# 3. 关键的排序步骤
cows.sort(key=lambda tp: tp[0])# 4. 滑动窗口初始化
cnt = defaultdict(int) # 品种计数器,对应我们分析中的 counts
have = 0               # 当前窗口的品种数
ans = 10**9            # 最小成本,初始化为极大值# tail 是右指针 j。这里代码的实现是 for 循环 i,while 循环 tail
# 它没有像我们分析中那样将 j 的初始化放在 i 循环外
# 而是通过一个 tail 变量来维护右指针的位置
tail = 0
# 先把第一头牛放进窗口,进行初始化
cnt[cows[0][1]] += 1
have = 1# 5. 窗口移动
# 主循环,i 是左指针
for i in range(n):# 扩展窗口:只要品种没齐,并且 tail 还能向右移动while have < total_type and tail + 1 < n:tail += 1 # 移动右指针p = cows[tail][1]cnt[p] += 1# 如果这个品种是新加入的,have++if cnt[p] == 1:have += 1# 如果当前窗口满足条件,更新答案if have == total_type:ans = min(ans, cows[tail][0] - cows[i][0])# 收缩窗口:将 i 指向的牛移出窗口p_left = cows[i][1]cnt[p_left] -= 1# 如果移出后,该品种数量为0,说明我们失去了一个品种if cnt[p_left] == 0:have -= 1# 这段代码是处理一个特殊情况:当左指针 i 追上甚至超过右指针 tail 时# 意味着窗口已经为空或者无效了。此时需要重置窗口。# 让 tail 指向 i 的下一个位置,并重置计数器和 have。# 这是一个实现上的选择,确保窗口始终至少有一个元素(如果可能)。# 在标准的双指针模板中,这个逻辑可能不这么写,但效果是类似的。if tail < i + 1 and i + 1 < n:tail = i + 1p = cows[tail][1]cnt = defaultdict(int) # 重置计数器cnt[p], have = 1, 1# 6. 输出结果
print(ans)
http://www.sczhlp.com/news/9214/

相关文章:

  • 8.10总结
  • 1960 - 2021 年全国气象数据分享
  • 实用指南:Apache Ignite Data Streaming 案例 StreamWords
  • 后缀数组 SA
  • Day39
  • 深度Ritz方法的全面误差分析
  • 给自己放假一天吧
  • matlab draw curves with shadows
  • 牛客周赛 Round 104
  • 「学习笔记」tarjan
  • 题解:CF1720E Misha and Paintings
  • tryhackme--Anonymous靶场wp
  • 记录Linux下beep命令不发声
  • 5335 | CASIO卡西欧官方网站
  • 基于Flask + Vue3 的新闻数据分析平台源代码+数据库+采用说明,爬取今日头条新闻数据,采集与清洗、数据分析、建立数据模型、数据可视化
  • Java创建图形化界面
  • 前端常见面试题
  • 升级openssh以及openssl
  • 《唐雎不辱使命》
  • 防止C语言头文件被重复包含 — ifndef #pragma once
  • 大型动作模型LAM:让企业重复任务实现80%效率提升的AI技术架构与实现方案
  • 通过移民局小程序下载出入境记录文件的详细流程
  • EXPLAIN和PROFILE
  • 炒饭新至
  • 8月10号
  • 2025年8月10日
  • Atcoder 和 Codeforces 入门指南
  • 4-2 排序算法 O(nlgn)
  • wqs二分
  • GPT-5技术解析:多版本模型与软件生成能力