题目描述
三角形是具有三条边且严格正面积的多边形。格点三角形是所有顶点坐标均为整数的三角形。在这个问题中,你需要计算一个 M×NM \times NM×N 网格中的格点三角形数量。
例如,在 1×21 \times 21×2 的网格中,有 181818 个不同的格点三角形。
输入格式
输入文件最多包含 212121 组输入。
每组输入包含两个整数 MMM 和 NNN(0<M,N≤10000 < M, N \leq 10000<M,N≤1000)。这两个整数表示你需要在一个 M×NM \times NM×N 的网格中数三角形。
输入以 MMM 和 NNN 均为 000 的情况结束,这种情况不应被处理。
输出格式
对于每组输入,输出一行。输出包含输出序号,后跟 M×NM \times NM×N 网格中的格点三角形数量。你可以假设三角形数量适合 646464 位有符号整数。
题目分析
问题本质
我们需要计算在 (M+1)×(N+1)(M+1) \times (N+1)(M+1)×(N+1) 的格点网格中,选择三个不共线的格点构成三角形的数量。
关键观察
-
总组合数:从所有格点中任选三个点的组合数为 C(M+1)(N+1)3C_{(M+1)(N+1)}^3C(M+1)(N+1)3
-
无效情况:三个点共线时无法构成三角形,需要减去这些情况
-
共线情况分类:
- 水平共线:同一行上的三个点
- 垂直共线:同一列上的三个点
- 斜线共线:同一斜线上的三个点
解题思路
1. 总体框架:容斥原理
使用容斥原理计算有效三角形数量:
有效三角形数=总三点组合数−水平共线数−垂直共线数−斜线共线数 \text{有效三角形数} = \text{总三点组合数} - \text{水平共线数} - \text{垂直共线数} - \text{斜线共线数} 有效三角形数=总三点组合数−水平共线数−垂直共线数−斜线共线数
2. 各项计算
2.1 总三点组合数
total=C(M+1)×(N+1)3 \texttt{total} = C_{(M+1) \times (N+1)}^3 total=C(M+1)×(N+1)3
从所有 (M+1)×(N+1)(M+1) \times (N+1)(M+1)×(N+1) 个格点中任选 333 个点。
2.2 水平共线数
horizontal=CM+13×(N+1) \texttt{horizontal} = C_{M+1}^3 \times (N+1) horizontal=CM+13×(N+1)
- 每行有 M+1M+1M+1 个点,选择 333 个点:CM+13C_{M+1}^3CM+13
- 共有 N+1N+1N+1 行
2.3 垂直共线数
vertical=CN+13×(M+1) \texttt{vertical} = C_{N+1}^3 \times (M+1) vertical=CN+13×(M+1)
- 每列有 N+1N+1N+1 个点,选择 333 个点:CN+13C_{N+1}^3CN+13
- 共有 M+1M+1M+1 列
3. 难点:斜线共线计算
3.1 数学基础
对于任意两个格点 (x1,y1)(x_1,y_1)(x1,y1) 和 (x2,y2)(x_2,y_2)(x2,y2):
- 设 dx=∣x2−x1∣dx = |x_2 - x_1|dx=∣x2−x1∣, dy=∣y2−y1∣dy = |y_2 - y_1|dy=∣y2−y1∣
- 线段上的格点数为:gcd(dx,dy)+1\gcd(dx, dy) + 1gcd(dx,dy)+1
- 中间格点数为:gcd(dx,dy)−1\gcd(dx, dy) - 1gcd(dx,dy)−1
关键洞察:每个中间格点与两个端点形成一个三点共线组合。
3.2 动态规划解法
使用双重前缀和技巧来统计所有斜线共线情况:
第一重前缀和:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + gcd(i, j) - 1
计算在 [0,i]×[0,j][0,i] \times [0,j][0,i]×[0,j] 范围内,所有从原点出发的线段上的中间格点数之和。
第二重前缀和:
dp[i][j] += dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1]
计算在 [0,i]×[0,j][0,i] \times [0,j][0,i]×[0,j] 范围内,所有任意起点的线段上的中间格点数之和。
3.3 最终斜线共线数
slant=dp[M][N]×2 \texttt{slant} = dp[M][N] \times 2 slant=dp[M][N]×2
- dp[M][N]dp[M][N]dp[M][N]:在 M×NM \times NM×N 网格中的所有斜线共线情况
- ×2\times 2×2:考虑斜线的正负两个方向
4. 算法流程
- 预处理:计算 dpdpdp 数组,使用双重前缀和
- 对于每个测试用例:
- 计算总三点组合数
- 减去水平共线数
- 减去垂直共线数
- 减去斜线共线数
- 输出结果
5. 复杂度分析
- 预处理:O(10002)O(1000^2)O(10002),可接受
- 每次查询:O(1)O(1)O(1)
- 总体:高效处理多组测试数据
代码实现
// Counting Triangles
// UVa ID: 12075
// Verdict: Accepted
// Submission Date: 2025-11-24
// UVa Run Time: 0.040s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
const int MAX = 1005;
int64 dp[MAX][MAX];
int64 comb3(int64 n) {
if (n < 3) return 0;
return n * (n - 1) * (n - 2) / 6;
}
void initialize() {
for (int i = 2; i < MAX; ++i)
for (int j = 2; j < MAX; ++j)
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + __gcd(i, j) - 1;
for (int i = 2; i < MAX; ++i)
for (int j = 2; j < MAX; ++j)
dp[i][j] += dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1];
}
int main() {
initialize();
int m, n;
int caseNum = 1;
while (cin >> m >> n && (m || n)) {
m++; n++;
int64 total = comb3(m * n);
int64 horizontal = comb3(m) * n;
int64 vertical = comb3(n) * m;
int64 slant = dp[m - 1][n - 1] * 2;
int64 result = total - horizontal - vertical - slant;
cout << "Case " << caseNum++ << ": " << result << endl;
}
return 0;
}
总结
本题的关键在于使用容斥原理和动态规划技巧来高效计算不共线的三点组合数。通过双重前缀和的设计,我们能够完整且不重复地统计所有斜线共线情况,从而得到正确的三角形数量。
这种组合数学与动态规划结合的方法,在处理格点计数问题时非常有效,值得学习和掌握。

432

被折叠的 条评论
为什么被折叠?



