题解:洛谷 P9125 Cow-libi
【题目来源】
洛谷:P9125 [USACO23FEB] Cow-libi S - 洛谷
【题目描述】
注意:本题的时间限制为 4 秒,是默认限制的两倍。
有人在 Farmer John 的 \(G(1 \leq G \leq 10^5)\) 个私人花园里偷吃了庄稼!通过他的专业法医知识,Farmer John 确定了每个花园被偷吃的具体时间。他还发现,这些事件的罪魁祸首是一头单独的奶牛。
为了回应这些犯罪行为,Farmer John 的 \(N(1 \leq N \leq 10^5)\) 头奶牛每头都提供了一个不在作案现场的证明(即“不在场证明”),表明奶牛在特定时间出现在特定位置。请帮助 Farmer John 判断这些“不在场证明”中哪些能够证明奶牛的清白。
如果一头奶牛无法在她的“不在场证明”位置与所有被偷吃地点之间往返,则可以确定这头奶牛是清白的。奶牛的移动速度为每单位时间 \(1\) 单位距离。本题中提到的距离均为欧几里得距离。
【输入】
第一行包含两个用空格分隔的整数 \(G\) 和 \(N\)。
接下来的 \(G\) 行,每行包含三个用空格分隔的整数 \(x, y, t\)(\(-10^9 \leq x, y \leq 10^9; 0 \leq t \leq 10^9\)),描述某次偷吃事件的地点和时间。可以保证单独一头奶牛可以在所有被偷吃地点之间往返。
接下来的 \(N\) 行,每行包含 \(x, y, t\)(\(-10^9 \leq x, y \leq 10^9; 0 \leq t \leq 10^9\)),描述某头奶牛的“不在场证明”中提到的位置和时间。
【输出】
输出一个整数,表示能够证明清白的奶牛的数量。
【输入样例】
2 4
0 0 100
50 0 200
0 50 50
1000 1000 0
50 0 200
10 0 170
【输出样例】
2
【核心思想】
-
问题分析:给定 \(G\) 个偷吃事件的时空坐标 \((x_i, y_i, t_i)\) 和 \(N\) 头奶牛的不在场证明 \((cowx, cowy, cowt)\)。奶牛速度为 \(1\),若某奶牛无法从证明位置在时间内到达任一偷吃地点,则证明其清白。本质上是几何 + 二分查找问题:判断奶牛与偷吃事件时空点之间的距离是否大于时间差。
-
算法选择:
- 按时间排序:将偷吃事件按时间 \(t\) 排序
- 二分定位:对每头奶牛,二分找到其证明时间 \(cowt\) 在偷吃事件时间序列中的位置
- 距离判定:只需检查 \(cowt\) 前后最近的两个偷吃事件,若均无法到达则奶牛清白
-
关键步骤:
- 读取数据:读入 \(G, N\) 和 \(G\) 个偷吃事件,按时间排序
- 处理每头奶牛:
- 若 \(cowt < g[1].t\):检查是否能到达最早偷吃点,不能则 \(ans \leftarrow ans + 1\)
- 若 \(cowt > g[G].t\):检查是否能到达最晚偷吃点,不能则 \(ans \leftarrow ans + 1\)
- 否则二分找到最大的 \(res\) 使得 \(g[res].t < cowt\)
- 检查奶牛是否能到达 \(g[res]\) 和 \(g[res+1]\):
- 欧几里得距离平方 \(> (time\_diff)^2\) 则无法到达
- 若至少一个无法到达,
innocent = true,\(ans \leftarrow ans + 1\)
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(G \log G + N \log G)\),排序 + 每头奶牛二分
- 空间复杂度:\(O(G)\),存储偷吃事件
-
几何 + 二分查找的核心思想:
- 时间单调性:偷吃事件按时间排序后,奶牛若无法到达 \(cowt\) 前后最近的两个事件,则无法到达任何事件(因为其他事件在时间上更远)
- 欧几里得距离判定:速度为 \(1\),可到达当且仅当 \(\sqrt{(x_1-x_2)^2+(y_1-y_2)^2} \leq |t_1-t_2|\),为避免浮点运算比较距离平方与时间差平方
- 边界处理:\(cowt\) 在所有事件之前或之后的情况单独处理,避免二分越界
- 清白的充分性:只需存在一个偷吃事件无法到达即可证明清白,但检查最近两个事件已足够
- 适用于时空分析、最近点查询、不在场证明类问题
【解题思路】

【算法标签】
普及 #整数二分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 因为题目中计算后会超int,所以全部修改为long long
int G, n, ans;
struct node {
int x, y, t;
bool operator < (node other) { // 后面用到sor排序,重载"<"
return t < other.t;
}
}g[100005];
signed main() // 前面有define,所以这里改为signed
{
cin >> G >> n; // 输入G和n
for (int i=1; i<=G; i++) { // 输入G个吃草的点
cin >> g[i].x >> g[i].y >> g[i].t;
}
sort(g+1, g+G+1); // 所有的点按照从小到大排序
int cowx, cowy, cowt;
for (int i=1; i<=n; i++) { // 输入每只奶牛的证词
cin >> cowx >> cowy >> cowt;
int res=0; // 定义二分答案
if (cowt<g[1].t) { // 如果cowt时刻小于g[1].t
if ((g[1].x-cowx)*(g[1].x-cowx)+(g[1].y-cowy)*(g[1].y-cowy) > (g[1].t-cowt)*(g[1].t-cowt)) { // 如果它没有足够的时间到达g[1]
ans++; // 就是无辜的
}
} else if (cowt>g[G].t) { // 如果cowt的时刻大于g[G].t
if ((g[G].x-cowx)*(g[G].x-cowx)+(g[G].y-cowy)*(g[G].y-cowy) > (g[G].t-cowt)*(g[G].t-cowt)) { // 如果它没有足够的时间到达g[G]
ans++; // 也是无辜的
}
} else {
int l = 1, r = G;
while (l<=r) { // 二分答案模板
int mid = (l+r)/2;
if (g[mid].t<cowt) { // g[mid].t小于cowt时(其他模板代码固定,可以开调试观察这里的结果)
res = mid; // 就更新res
l = mid+1; // 然后增加l,找到最大的l
} else { // 否则
r = mid-1; // 减少r
}
}
bool innocent = false; // 定义标记位
if ((g[res].x-cowx)*(g[res].x-cowx) + (g[res].y-cowy)*(g[res].y-cowy) > (g[res].t-cowt)*(g[res].t-cowt)) { // 如果无法到res这个点
innocent = true; // 修改标记位
}
if ((g[res+1].x-cowx)*(g[res+1].x-cowx) + (g[res+1].y-cowy)*(g[res+1].y-cowy) > (g[res+1].t-cowt)*(g[res+1].t-cowt)) { // 如果无法到res+1这个点
innocent = true; // 修改标记位
}
ans += innocent; // 只要有一个吃草点无法到达,就是无辜的
}
}
cout << ans << endl; // 输出无辜的奶牛数量
return 0;
}
【运行结果】
2 4
0 0 100
50 0 200
0 50 50
1000 1000 0
50 0 200
10 0 170
2
浙公网安备 33010602011771号