Walking Along a Fence

GitHub跳转原题关系图返回列表

把围栏按顺序逐格标成环形路径位置,查询时取两点标号差和补弧长的较小值。

OJ: usaco

题目 ID: 1420

难度:普及-

标签:模拟图形前缀和usaco

日期: 2026-07-11 15:44

题意

给定一个由水平、竖直线段组成的闭合围栏。每头奶牛给出围栏上的起点和终点。

因为围栏是一个环,从起点到终点有两条路,求较短的一条距离。

思路

先看一个更直观的路径展开写法:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 15:44
 * update_at: 2026-07-11 15:46
 */
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

const int MAXC = 1005;
const int MAXP = 200005;

int n, p;
int x_pos[MAXP], y_pos[MAXP];
int label[MAXC][MAXC];
vector<pair<int, int> > path_points; // 按围栏行走顺序记录所有整数点。

int sign(int x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    return 0;
}

void walk_segment(int x1, int y1, int x2, int y2) {
    int dx = sign(x2 - x1);
    int dy = sign(y2 - y1);
    int dist = abs(x2 - x1) + abs(y2 - y1);

    int x = x1;
    int y = y1;
    for (int step = 0; step < dist; step++) {
        path_points.push_back(make_pair(x, y));
        x += dx;
        y += dy;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> p;

    for (int x = 0; x < MAXC; x++) {
        for (int y = 0; y < MAXC; y++) {
            label[x][y] = -1;
        }
    }

    for (int i = 1; i <= p; i++) {
        cin >> x_pos[i] >> y_pos[i];
    }

    for (int i = 1; i <= p; i++) {
        int j = i + 1;
        if (j == p + 1) j = 1;
        walk_segment(x_pos[i], y_pos[i], x_pos[j], y_pos[j]);
    }

    for (int i = 0; i < (int)path_points.size(); i++) {
        int x = path_points[i].first;
        int y = path_points[i].second;
        label[x][y] = i;
    }

    int perimeter = (int)path_points.size();

    while (n--) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;

        int d = abs(label[x1][y1] - label[x2][y2]);
        cout << min(d, perimeter - d) << '\n';
    }

    return 0;
}

这个写法把围栏上的整数点按行走顺序保存下来。正式代码可以直接用二维数组记录每个点的编号。

把围栏看成一条环形路径。我们从第一根柱子开始,按输入顺序沿围栏走一圈,给每个经过的整数点打标号:

text
label[x][y] = 沿围栏走到 (x,y) 时已经走过的距离

同时得到围栏总长度 perimeter

对一个查询,设两个点的标号分别为 p1p2。沿一个方向走的距离是:

text
d = abs(p1 - p2)

另一条路绕环走剩下部分,长度是:

text
perimeter - d

答案就是:

text
min(d, perimeter - d)

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 15:44
 * update_at: 2026-07-11 15:46
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXC = 1005;
const int MAXP = 200005;

int n, p;
int x_pos[MAXP], y_pos[MAXP];
int label[MAXC][MAXC]; // label[x][y] 表示沿围栏走到点 (x,y) 的距离。
int perimeter;

int sign(int x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    return 0;
}

void walk_segment(int x1, int y1, int x2, int y2) {
    int dx = sign(x2 - x1);
    int dy = sign(y2 - y1);
    int dist = abs(x2 - x1) + abs(y2 - y1);

    int x = x1;
    int y = y1;
    for (int step = 0; step < dist; step++) {
        label[x][y] = perimeter;
        perimeter++;
        x += dx;
        y += dy;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> p;

    for (int x = 0; x < MAXC; x++) {
        for (int y = 0; y < MAXC; y++) {
            label[x][y] = -1;
        }
    }

    for (int i = 1; i <= p; i++) {
        cin >> x_pos[i] >> y_pos[i];
    }

    for (int i = 1; i <= p; i++) {
        int j = i + 1;
        if (j == p + 1) j = 1;
        walk_segment(x_pos[i], y_pos[i], x_pos[j], y_pos[j]);
    }

    while (n--) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;

        int d = abs(label[x1][y1] - label[x2][y2]);
        cout << min(d, perimeter - d) << '\n';
    }

    return 0;
}

复杂度

坐标范围上限为 10001000,预处理沿围栏逐格行走,复杂度可视为 O(10002)O(1000^2)

每个查询 O(1)O(1)

空间复杂度为 O(10002)O(1000^2)

总结

环上两点的最短距离,等于一段弧长和补弧长的较小值。

本题先把几何围栏转成一维环形路径编号,查询就变成了常数时间计算。