题解:洛谷 P2997 Banner
【题目来源】
洛谷:P2997 [USACO10NOV] Banner S
【题目描述】
Bessie 刚从国外长途旅行回来,农夫约翰想在她的牧场里竖起一个漂亮的「欢迎回家」横幅。横幅将挂在两根柱子之间的电线上,电线的长度范围是 \(L_1..L_2\),其中 \(1 \le L_1 \le L_2\),且 \(L_1 \le L_2 \le 1,500\)。
牧场的大小为 \(W \times H\),其中 \(1 \le W \le 1,000\),\(1 \le H \le 1,000\),农夫约翰在每个整数坐标点上都安装了一根柱子。在这些 \((W + 1) \times (H + 1)\) 个点中,农夫约翰必须选择两个点来固定电线的两端,以便悬挂横幅。
约翰希望横幅悬挂时不受干扰,并要求在他拉紧的电线下方没有柱子。
农夫约翰需要你的帮助来计算他有多少种可能的方式来悬挂横幅。他知道这个数量很大,32 位整数可能不足以计算出答案。
考虑下面的牧场示例,其中 \(W = 2\) 和 \(H = 1\):
* * *
* * *
横幅的长度范围是 \(2..3\)。这个牧场包含 \((2+1) \times (1+1) = 6\) 个点,并且有 \(\binom{6}{2} = \frac{6\times5}{2\times1} = 15\) 对不同的点对,可以在其间拉伸横幅固定电线:
(0,0)-(0,1) (0,0)-(2,1) (0,1)-(2,1) (1,1)-(2,0)
(0,0)-(1,0) (0,1)-(1,0) (1,0)-(1,1) (1,1)-(2,1)
(0,0)-(1,1) (0,1)-(1,1) (1,0)-(2,0) (2,0)-(2,1)
(0,0)-(2,0) (0,1)-(2,0) (1,0)-(2,1)
在这些点对中,只有四对的长度在 \(2..3\) 的范围内:
长度 长度
(0,0)-(2,0) 2.00 (0,1)-(2,0) 2.24
(0,0)-(2,1) 2.24 (0,1)-(2,1) 2.00
在这四对中,点对 (0,0)-(2,0) 和 (0,1)-(2,1) 的连线上都有柱子,因此不合适。
所以,在 15 对点中,只有两对是合适的悬挂横幅电线的候选者。
【输入】
* 第 1 行:四个用空格分隔的整数:\(W\),\(H\),\(L_1\) 和 \(L_2\)。
【输出】
* 第 1 行:一个整数,表示可能的横幅数量。
【输入样例】
2 1 2 3
【输出样例】
2
【核心思想】
-
问题分析:在一个 \((W+1) \times (H+1)\) 的整数点阵中,选择两个点作为电线端点,要求两点间距离在 \([L_1, L_2]\) 范围内,并且两点连线不经过任何其他整数点(即端点之间无其他柱子)。求满足条件的点对数量。本质是枚举所有可能的相对位移向量 \((i, j)\),并利用 \(i\) 与 \(j\) 互质 来保证线段不经过中间整点。
-
算法选择:
- 枚举向量 + 互质判定:直接枚举两点在水平和垂直方向上的差值 \((i, j)\)(\(i \in [0, W]\),\(j \in [0, H]\),排除 \(i=j=0\))。对于每个非零向量,其长度平方为 \(i^2 + j^2\),若在 \([L_1^2, L_2^2]\) 之间,且 \(\gcd(i, j) = 1\),则该向量对应的所有点对均合法。
- 计数策略:对于固定的 \((i, j)\),在 \(W \times H\) 的网格中,水平方向有 \((W+1-i)\) 种起点列位置,垂直方向有 \((H+1-j)\) 种起点行位置,因此共有 \((W+1-i)(H+1-j)\) 个有序方向的点对。由于 \((i, j)\) 和 \((-i, -j)\) 代表同一对点(但起点和终点互换),代码中通过乘以 \(2\) 来处理正负方向(当 \(i \neq 0\) 且 \(j \neq 0\) 时)。
- 特殊情况:水平相邻(\(i=1, j=0\))和垂直相邻(\(i=0, j=1\))的点对,如果长度在 \([L_1, L_2]\) 范围内(即 \(L_1 \le 1 \le L_2\)),需单独计数,因为代码中的双层循环从 \(i=1, j=1\) 开始,不包含 \(i=0\) 或 \(j=0\) 的情况。
-
关键步骤:
- 读入:\(W, H, L_1, L_2\)。
- 特判相邻点对:若 \(L_1 \le 1\) 且 \(L_2 \ge 1\),则水平相邻点对数为 \(W \times (H+1)\),垂直相邻点对数为 \(H \times (W+1)\),累加至答案。
- 枚举斜向量:\(i\) 从 \(1\) 到 \(W\),\(j\) 从 \(1\) 到 \(H\):
- 计算距离平方 \(t = i^2 + j^2\)。
- 若 \(t < L_1^2\) 或 \(t > L_2^2\),跳过。
- 若 \(\gcd(i, j) \neq 1\),跳过(连线经过中间整点)。
- 若合法,累加 \(2 \times (W+1-i) \times (H+1-j)\) 到答案(两个方向)。
- 输出答案。
-
时间/空间复杂度:
- 时间复杂度:\(O(W \cdot H \cdot \log(\min(W, H)))\),其中 \(\log\) 来自 \(\gcd\) 计算,\(W, H \le 1000\),完全可行。
- 空间复杂度:\(O(1)\)。
-
枚举 + 互质判定的核心思想:
- 几何转换:将“两点间无其他柱子”等价为“水平和垂直位移互质”。因为线段经过的整数点个数为 \(\gcd(i, j) - 1\)(不包括端点),只有 \(\gcd(i, j) = 1\) 时,中间无整点。
- 长度条件:\(L_1 \le \sqrt{i^2+j^2} \le L_2\),为了避免浮点数误差,直接比较平方 \(L_1^2 \le i^2+j^2 \le L_2^2\)。
- 计数对称性:对于每个满足条件的 \((i, j)\),向量 \((i, -j)\) 也满足条件,且对应不同的点对,因此乘 \(2\)。对于水平或垂直向量,没有对称乘 \(2\) 的重复(因为方向相反的点对已被计入),所以需要单独处理。
- 适用场景:适用于格点问题中统计“可见点对”(两整点之间不经过其他整点)的数量,核心是利用 \(\gcd\) 判定可见性。
【算法标签】
普及 #数学
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用 long long 防止答案溢出
int w, h, l1, l2, ans; // 牧场宽、高,电线长度范围下限、上限,答案
// 求最大公约数,用于判断两点连线是否经过其他整点
int gcd(int a, int b)
{
if (b == 0)
return a;
else
return gcd(b, a % b);
}
signed main()
{
cin >> w >> h >> l1 >> l2;
// 特殊情况:长度范围为 1 时,处理水平或垂直的相邻柱子
// 此时两点连线长度为 1,且不可能经过其他柱子,直接计数
// 水平相邻点对:w 列间隙 * (h+1) 行
// 垂直相邻点对:h 行间隙 * (w+1) 列
if (l1 <= 1 && l2 >= 1)
ans = w * (h + 1) + h * (w + 1);
// 枚举两点在水平和垂直方向上的差值 (i, j)
// i 为水平距离(列差),j 为垂直距离(行差),且均大于 0,代表斜向或非相邻情况
for (int i = 1; i <= w; i++)
for (int j = 1; j <= h; j++)
{
// 计算实际欧几里得距离的平方
int t = i * i + j * j;
// 若距离平方不在 [l1^2, l2^2] 范围内,则长度不合要求,跳过
if (t > l2 * l2 || t < l1 * l1)
continue;
// 若 gcd(i, j) != 1,说明两点连线经过其他整数点(柱子),不合要求,跳过
if (gcd(i, j) != 1)
continue;
// 统计所有满足条件的点对
// 水平方向有 (w+1-i) 种起始列位置,垂直方向有 (h+1-j) 种起始行位置
// 每个 (i, j) 向量对应两种方向(斜率为正和斜率为负),故乘以 2
ans += 2 * (w + 1 - i) * (h + 1 - j);
}
cout << ans << endl;
return 0;
}
【运行结果】
2 1 2 3
2
浙公网安备 33010602011771号