把围栏按顺序逐格标成环形路径位置,查询时取两点标号差和补弧长的较小值。
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。
对一个查询,设两个点的标号分别为 p1 和 p2。沿一个方向走的距离是:
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;
}复杂度
坐标范围上限为
每个查询
空间复杂度为
总结
环上两点的最短距离,等于一段弧长和补弧长的较小值。
本题先把几何围栏转成一维环形路径编号,查询就变成了常数时间计算。