题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛,每头牛在 \(t=0\) 时左端点位于 \((x, y)\),以速度 \(1/r\) 向右移动。FJ 站在 \((0,0)\) 沿 \(+y\) 方向观察,当某头牛在穿过 \(y\) 轴(\(x=0\))的某时刻成为最靠近观察者的可见牛时,FJ 能看到它。求 FJ 能看到的奶牛数量。这是一个区间覆盖/离散化 + 线段树问题,关键在于将每头牛在 \(y\) 轴上的可见时间区间转化为线段覆盖问题,按 \(y\) 坐标从小到大处理,用线段树维护已被覆盖的区间。

  2. 算法选择

    • 时间区间计算:奶牛 \(i\)\((x, y)\) 移动到右端点穿过 \(y\) 轴的时间区间为 \([(-x-1) \cdot r,\ -x \cdot r]\)(左闭右开)
    • 离散化:将所有区间端点坐标离散化,建立线段树
    • \(y\) 排序贪心:按 \(y\) 从小到大处理每头牛,若其时间区间在线段树中仍有未被覆盖的部分,则 FJ 能看到它,并将该区间覆盖
  3. 关键步骤

    • 读取输入\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),离散化 \(O(N \log N)\),线段树操作 \(O(N \log N)\)
    • 空间复杂度:\(O(N)\),离散化数组和线段树
  5. 线段树区间覆盖的核心思想

    • 可见性转化: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
posted @ 2026-08-28 15:57  团爸讲算法  阅读(3)  评论(0)    收藏  举报