题解:洛谷 P9957 Stuck in a Rut

【题目来源】

洛谷:P9957 [USACO20DEC] Stuck in a Rut B - 洛谷

【题目描述】

Farmer John 最近扩大了他的农场,从奶牛们的角度看来这个农场相当于是无限大了!奶牛们将农场上放牧的区域想作是一个由正方形方格组成的无限大二维方阵,每个方格中均有美味的草(将每个方格看作是棋盘上的一个方格)。Farmer John 的 \(N\) 头奶牛(\(1\le N\le 50\))初始时位于不同的方格中,一部分朝向北面,一部分朝向东面。

每一小时,每头奶牛会执行以下二者之一:

  • 如果她当前所在的方格里的草已经被其他奶牛吃掉了,则她会停下。
  • 吃完她当前所在的方格中的所有草,并向她朝向的方向移动一个方格。

经过一段时间,每头奶牛的身后会留下一条被啃秃了的轨迹。

如果两头奶牛在一次移动中移动到了同一个有草的方格,她们会分享这个方格中的草,并在下一个小时继续沿她们朝向的方向移动。

请求出每头奶牛吃到的草的数量。有些奶牛永远不会停下,从而吃到无限多的草。

【输入】

输入的第一行包含 \(N\)。以下 \(N\) 行,每行描述一头奶牛的起始位置,包含一个字符 N(表示朝向北面)或 E(表示朝向东面),以及两个非负整数 \(x\)\(y\)\(0\le x\le 10^9\)\(0\le y\le 10^9\))表示方格的坐标。所有 \(x\) 坐标各不相同,所有 \(y\) 坐标各不相同。

为了使方向和坐标尽可能明确,如果一头奶牛位于方格 \((x,y)\) 并向北移动,她会到达方格 \((x,y+1)\)。如果她向东移动,她会到达方格 \((x+1,y)\)

【输出】

输出 \(N\) 行。输出的第 \(i\) 行包含输入中的第 \(i\) 头奶牛吃到草的方格的数量。如果一头奶牛可以吃到无限多的草,为这头奶牛输出 Infinity

【输入样例】

6
E 3 5
N 5 3
E 4 6
E 10 4
N 11 2
N 8 1

【输出样例】

5
3
Infinity
Infinity
2
5

【核心思想】

  1. 问题分析:在无限网格上,\(N \le 50\) 头奶牛初始位于不同方格,方向仅向北(N)或向东(E)。每头奶牛每小时先吃当前格子的草,然后沿方向移动一格;若移动到的格子草已被吃掉,则停下。若两头牛同时移动到同一有草格子,则共享该格草并继续前进。求每头牛最终吃到的草量,若无限则输出 Infinity。本质是一个带有阻塞和共享的定向移动模拟问题,关键在于:一头牛被另一头牛“挡住”当且仅当它们的路径交叉,且交叉点先被挡住它的牛吃到。

  2. 算法选择

    • 按距离排序 + 逐个判定:将所有牛按 \(x+y\) 从大到小排序(即离原点较远的先处理)。因为只有 \(x+y\) 较小的牛才可能被较大的牛挡住(路径交叉点距离原点更近)。处理时,对于每头牛,检查所有已处理(即更远)且方向垂直的牛,计算可能的阻挡距离,取最小值。
    • 分方向处理
      • 对于向东的牛 \(i\),会被向北的牛 \(j\) 挡住,当且仅当 \(j\) 的路径与 \(i\) 的路径相交,且交点 \((x_j, y_i)\)\(i\) 的前方(\(x_j > x_i\))且在 \(j\) 的前方(\(y_i > y_j\))。阻挡距离为 \(x_j - x_i\)
      • 对于向北的牛 \(i\),会被向东的牛 \(j\) 挡住,类似条件为 \(y_j > y_i\)\(x_i > x_j\),阻挡距离为 \(y_j - y_i\)
    • 处理无限情况:若一头牛未被任何牛挡住,则答案为 Infinity
  3. 关键步骤

    • 数据结构:存储每头牛的方向 d、坐标 (x, y)、原始编号 id、答案 ans(初始化为一个大数 INF)。
    • 排序:按 \(x+y\) 降序排列(确保先处理可能被挡住、但位置较远的牛)。
    • 逐头判定(按排序后的顺序):
      • 若当前牛 \(i\) 方向为 E,遍历所有已处理(\(j < i\))且方向为 N 的牛 \(j\)
        • 跳过对角线情况(\(x_i + y_i == x_j + y_j\))。
        • \(y_i < y_j\),跳过(交点不在 \(j\) 的前方)。
        • \(j\) 的答案为 INF\(j\) 能到达交点(\(y_j + ans_j > y_i\)),则更新 \(ans_i = \min(ans_i, x_j - x_i)\)
      • 若当前牛 \(i\) 方向为 N,对称处理(遍历方向为 E\(j\)):
        • 跳过对角线。
        • \(x_i < x_j\),跳过。
        • \(j\) 的答案为 INF\(x_j + ans_j > x_i\),则更新 \(ans_i = \min(ans_i, y_j - y_i)\)
    • 恢复原始顺序并输出:按 id 排序回原顺序,若 ans == INF 输出 Infinity,否则输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N^2)\)\(N \le 50\)
    • 空间复杂度:\(O(N)\)
  5. 排序 + 逐个阻挡判定的核心思想

    • 路径交叉模型:向东的牛 \(i\) 路径为 \((x_i + t, y_i)\),向北的牛 \(j\) 路径为 \((x_j, y_j + t)\)。它们相交当且仅当 \(x_j \ge x_i\)\(y_i \ge y_j\),交点坐标为 \((x_j, y_i)\)
    • 阻挡条件:牛 \(i\) 被牛 \(j\) 挡住,当且仅当 \(i\) 到达交点时,\(j\) 已经吃过该格(即 \(j\) 到达交点的时间更早或同时)。代码中利用已处理牛 \(j\) 的答案来判断其是否能到达交点。
    • 无限情况:如果 \(i\) 在前进方向上没有遇到任何能先到达交点的垂直方向牛,则 \(i\) 可以无限前进。
    • 排序的作用:按 \(x+y\) 降序处理,保证当处理 \(i\) 时,所有可能挡住 \(i\) 的牛(坐标更大)已经被处理,其答案已经确定。这是正确性保证。
    • 适用场景:适用于有限数量物体沿正交方向匀速运动,需要判断相遇和阻挡顺序的问题。核心是排序处理以消除时间依赖。

【解题思路】

【算法标签】

普及 #其他排序

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n;
struct node {
    char d;
    int x, y;
    int id;
    int ans;
}a[55];
bool cmp(node a, node b)
{
    return a.x+a.y > b.x+b.y;
}
bool cmp2(node a, node b)
{
    return a.id < b.id;
}
int main()
{
    cin >> n;  // 输入n
    for (int i=1; i<=n; i++) {  // 遍历n头牛
        cin >> a[i].d >> a[i].x >> a[i].y;  // 记录n头牛的方向,起始坐标
        a[i].id = i;  // 牛的编号
        a[i].ans = 1e9;  // 以及能吃的草的数量,初始为1e9
    }
    sort(a+1, a+n+1, cmp);  // 按照x+y从大到小的方式排序(因为x+y较小的可能会被较大者挡住前路)
    for (int i=1; i<=n; i++) {  // 遍历n头牛
        if (a[i].d == 'E') {  // 如果当前牛的方向为E
            for (int j=1; j<i; j++) {  // 遍历i之前的牛
                if (a[j].d=='N') {  // 如果牛的方向为N
                    if (a[i].x + a[i].y == a[j].x + a[j].y) continue;  // 如果牛为对角线的牛,则跳过,可能不会互相妨碍,或者共享同一方格的草
                    if (a[i].y<a[j].y) continue;  // 如果牛在i牛的上方,肯定不会妨碍
                    if (a[j].ans==1e9) {  // 如果j牛可以一直走下去
                        a[i].ans = min(a[i].ans, a[j].x-a[i].x);  // 那就计算两者之间x的差值,并取最小值
                    } else if (a[j].y+a[j].ans>a[i].y) {  // 如果j牛已经把i牛右侧的草给吃了
                        a[i].ans = min(a[i].ans, a[j].x-a[i].x);  // i牛能吃的草就只有a[j].x-a[i].x了
                    }
                }
            }
        }
        if (a[i].d == 'N') {  // 如果当前牛的方向为N
            for (int j=1; j<i; j++) {  // 遍历i之前的牛
                if (a[j].d=='E') {  // 如果牛的方向为E
                    if (a[i].x + a[i].y == a[j].x + a[j].y)continue;  // 如果牛为对角线的牛,则跳过,可能不会互相妨碍,或者共享同一方格的草
                    if (a[i].x<a[j].x) continue;  // 如果牛在i牛的上方,肯定不会妨碍
                    if (a[j].ans==1e9) {  // 如果j牛可以一直走下去
                        a[i].ans = min(a[j].y-a[i].y, a[i].ans);  // 那就计算两者之间y的差值,并取最小值
                    } else if (a[j].x+a[j].ans>a[i].x) {  // j牛已经把i牛上方的草给吃了
                        a[i].ans = min(a[j].y-a[i].y, a[i].ans);  // i牛能吃的草就只有a[j].y-a[i].y了
                    }
                }
            }
        }
    }
    sort(a+1, a+n+1, cmp2);  // 再次对a数组进行排序,按照id从小到大的方式排序
    for (int i=1; i<=n; i++) {  // 遍历n头奶牛
        if (a[i].ans==1e9) {  // 如果ans为1e9
            cout << "Infinity" << endl;  // 则输出Inifinity
        } else cout << a[i].ans << endl;  // 否则正常输出
    }
    return 0;
}

【运行结果】

6
E 3 5
N 5 3
E 4 6
E 10 4
N 11 2
N 8 1
5
3
Infinity
Infinity
2
5
posted @ 2026-08-30 09:20  团爸讲算法  阅读(5)  评论(0)    收藏  举报