题解:洛谷 P3114 Stampede
【题目来源】
洛谷:P3114 [USACO15JAN] Stampede S - 洛谷
【题目描述】
FJ 的 \(N\) 头奶牛(\(1 \leq N \leq 50,000\))看似在农场前的路上狂奔,实际上它们正在进行一场赛跑。
从上方俯视,每头牛在时间 \(t = 0\) 时被表示为一个单位长度的水平线段,其左端点坐标为 \((x, y)\)。例如,\((-3, 6)\) 表示一头在 \(t = 0\) 时从 \((-3, 6)\) 延伸到 \((-2, 6)\) 的奶牛。每头牛以一定速度向右(\(+x\) 方向)移动,该速度由移动 1 单位距离所需的整数时间 \(r\) 描述。
FJ 并不满意他的奶牛在外赛跑而不在牛棚产奶。他计划在比赛结束后训斥参赛的奶牛。为了确定哪些奶牛参赛,FJ 站在 \((0, 0)\) 处并沿 \(+y\) 方向的射线观察。当一头牛在某个时刻成为这条射线上首个可见的牛时,FJ 就会看到它。如果一头牛在穿过 FJ 视线期间始终被其他牛"挡住",则她不可见。
请计算 FJ 在整个比赛过程中能看到的奶牛数量。
【输入】
第一行包含 \(N\)。接下来 \(N\) 行每行描述一头牛,包含三个整数 \(x\) \(y\) \(r\),分别表示:左端点初始坐标为 \((x, y)\),以每移动 1 单位距离需要 \(r\) 单位时间的速度向右移动。\(x\) 的取值范围为 \(-1000\) 到 \(-1\),\(y\) 的取值范围为 \(1\) 到 \(1,000,000\)(所有牛的 \(y\) 值互不相同以避免碰撞),\(r\) 的取值范围为 \(1\) 到 \(1,000,000\)。
【输出】
一个整数,表示 FJ 能看到的奶牛数量。
【输入样例】
3
-2 1 3
-3 2 3
-5 100 1
【输出样例】
2
【核心思想】
-
问题分析:给定 \(N\) 头奶牛,每头牛在 \(t=0\) 时左端点位于 \((x, y)\),以速度 \(1/r\) 向右移动。FJ 站在 \((0,0)\) 沿 \(+y\) 方向观察,当某头牛在穿过 \(y\) 轴(\(x=0\))的某时刻成为最靠近观察者的可见牛时,FJ 能看到它。求 FJ 能看到的奶牛数量。这是一个区间覆盖/离散化 + 线段树问题,关键在于将每头牛在 \(y\) 轴上的可见时间区间转化为线段覆盖问题,按 \(y\) 坐标从小到大处理,用线段树维护已被覆盖的区间。
-
算法选择:
- 时间区间计算:奶牛 \(i\) 从 \((x, y)\) 移动到右端点穿过 \(y\) 轴的时间区间为 \([(-x-1) \cdot r,\ -x \cdot r]\)(左闭右开)
- 离散化:将所有区间端点坐标离散化,建立线段树
- 按 \(y\) 排序贪心:按 \(y\) 从小到大处理每头牛,若其时间区间在线段树中仍有未被覆盖的部分,则 FJ 能看到它,并将该区间覆盖
-
关键步骤:
- 读取输入:\(N\) 和每头牛的 \((x, y, r)\)
- 计算时间区间:\(l_i = (-x-1) \cdot r\),\(r_i = -x \cdot r\)(左闭右开)
- 离散化:收集所有 \(l_i\) 和 \(r_i\),排序去重后映射为线段树坐标
- 按 \(y\) 升序排序奶牛
- 线段树处理:
- 对每个奶牛查询其 \([l_i, r_i)\) 区间是否还有未覆盖部分
- 若有:\(ans \mathrel{+}= 1\),并将该区间标记为已覆盖
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),离散化 \(O(N \log N)\),线段树操作 \(O(N \log N)\)
- 空间复杂度:\(O(N)\),离散化数组和线段树
-
线段树区间覆盖的核心思想:
- 可见性转化:FJ 沿 \(+y\) 方向观察,\(y\) 坐标小的牛会遮挡 \(y\) 坐标大的牛。因此按 \(y\) 从小到大处理,先处理矮牛再处理高牛
- 时间区间冲突:两头牛若在同一时刻穿过 \(y\) 轴,\(y\) 小的遮挡 \(y\) 大的。将每头牛的穿过时间转化为区间,问题变为"区间覆盖计数"
- 线段树维护覆盖:支持区间查询(是否有剩余未覆盖)和区间覆盖标记,高效处理大量区间操作
- 适用于"二维遮挡 + 区间覆盖"类问题,核心是将几何遮挡转化为时间区间覆盖问题
【算法标签】
提高+ #线段树
【代码详解】
// 使用区间覆盖+离散化实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 400005;
struct Cow {
int l, r, s;
}a[N];
int n, t[N], cnt, c[N], d[N];
signed main()
{
cin >> n;
for (int i=1; i<=n; i++) {
int x, y, z;
cin >> x >> y >> z;
x = -x;
a[i].l = (x-1)*z;
a[i].r = a[i].l+z-1;
a[i].s = y;
t[++cnt] = a[i].l;
t[++cnt] = a[i].r;
t[++cnt] = a[i].r+1; // 必须要加这一条
}
// 离散化(排序+去重)
sort(t+1, t+cnt+1);
int len = unique(t+1, t+cnt+1)-(t+1); // STL去重
memset(c, 0x3f, sizeof(c));
for (int i=1; i<=n; i++) { // 重新赋值
a[i].l = lower_bound(t+1, t+len+1, a[i].l)-t;
a[i].r = lower_bound(t+1, t+len+1, a[i].r)-t;
for (int j=a[i].l; j<=a[i].r; j++) { // 将a[i]的[l,r]区间的奶牛编号设置为最小y的编号,例如区间[1,3]为1,[4,5]为2
if (c[j]>a[i].s) {
c[j] = a[i].s; // c 记录奶牛的最小y值
d[j] = i; // d 记录最小y值的奶牛的编号
}
}
}
int ans = 0;
for (int i=1; i<=n; i++) {
for (int j=a[i].l; j<=a[i].r; j++) {
if (d[j]==i) {
ans++; //如果第 i 头奶牛经过 y 轴的时间内能被看到,答案+1
break;
}
}
}
cout << ans << endl;
return 0;
}
// 使用线段树+离散化实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lc p<<1
#define rc p<<1|1
int n, cnt, b[100005], tot, ans;
struct Cow {
int l, r, s;
}a[50005];
map<int, int> m;
struct Node {
int l, r;
bool v;
}tr[8000005];
void add(int x, int y, int z, int k)
{
a[k].l = (x-1)*z;
a[k].r = x*z;
a[k].s = y;
b[++cnt] = (x-1)*z;
b[++cnt] = x*z;
}
bool cmp(Cow x, Cow y)
{
return x.s < y.s;
}
void build(int p, int l, int r)
{
tr[p] = {l, r, 0};
if (l==r) return; //是叶子则返回
int m = l+r >> 1; //不是叶子则裂开
build(lc, l, m);
build(rc, m+1, r);
}
int query(int p, int x, int y)
{
// 处理区间覆盖
if (tr[p].v==1) return 0; // 如果这段区间已被覆盖,则返回0
if (x<=tr[p].l && tr[p].r<=y) { // 走到这里说明没有被覆盖,且在区间内
tr[p].v = 1; // 将区间覆盖
return 1; // 返回1
}
int ls = 0, rs = 0;
int m = tr[p].l+tr[p].r >> 1; //不覆盖则裂开
if (x<=m) ls += query(lc, x, y);
if (y>m) rs += query(rc, x, y);
if (tr[lc].v && tr[rc].v) tr[p].v = 1; // 如果左右子节点都被覆盖,则该节点也被覆盖
else tr[p].v = 0; // 如果有任一子节点没被覆盖,则也未覆盖
if (ls==1 || rs==1) return 1;
else return 0;
}
signed main()
{
cin >> n;
for (int i=1; i<=n; i++) {
int x, y, z;
cin >> x >> y >> z;
add(-x, y, z, i);
}
sort(b+1, b+1+cnt);
sort(a+1, a+1+n, cmp); // 按每只羊驼的y轴高度从小到大排序
for (int i=1; i<=n*2; i++) {
if (b[i]!=b[i-1] || i==1) m[b[i]] = ++tot; // 离散化去重
}
build(1, 1, tot);
for (int i=1; i<=n; i++) { // 离散化后重新赋值
a[i].l = m[a[i].l];
a[i].r = m[a[i].r];
a[i].r--; // 左闭右开区间
}
for (int i=1; i<=n; i++) {
int x = query(1, a[i].l, a[i].r);
ans += x;
}
cout << ans;
return 0;
}
【运行结果】
3
-2 1 3
-3 2 3
-5 100 1
2
浙公网安备 33010602011771号