usaco-3.4.3 电网

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

用 Pick 定理数三角形 (0,0),(n,m),(p,0) 内部格点:2S=pm,边界 B=gcd(n,m)+gcd(|p-n|,m)+p。

OJ: roj

题目 ID: 2059

难度:普及+/提高-

标签:数学计算几何Pick定理数论gcdpython

创建: 2026-10-01 06:07

更新: 2026-10-07 13:50

形式化题目

给定三个顶点 O(0,0)O(0,0)、A(n,m)A(n,m)、C(p,0)C(p,0)(0⩽n<320000\leqslant n<32000,0<m<320000<m<32000,0<p<320000<p<32000), 它们都是整点。求严格落在三角形 OACOAC 内部(不含三条边)的整点个数。

输入一行三个整数 n m pn\ m\ p,输出一个整数表示内部整点数。

关于原题面

本题在 ROJ 题面页上残留了 USACO「fence3(Electric Fences)」的描述,但配套的 12 组测试数据(文件名 fence90 ~ fence911,配置标题「usaco-3.4.3」)全部是 USACO 3.4「fence9」的数据,即本题实际要解的「三角形内部整点数」。本题解按真实 测试数据编写。

正解

思路

朴素做法与它的瓶颈

最直接的做法是圈出三角形的包围盒 0⩽x⩽max⁡(n,p)0\leqslant x\leqslant \max(n,p)、0⩽y⩽m0\leqslant y\leqslant m, 逐个枚举格点,再用叉积判断它是否在三角形内部。

包围盒规模最坏是 3×104×3×104=9×1083\times10^4 \times 3\times10^4 = 9\times10^8 个格点。 这不只是「Python 慢」的常数问题——逐点枚举这个建模方式本身就是 O(nm)O(nm) 的, 换 C++ 也不划算。要真正提速,必须把「数格点」换成「算几何量」。

关键观察:顶点全为格点时,Pick 定理把计数变成代数

Pick 定理说:对顶点全为格点的简单多边形,

S=I+B2−1,S = I + \frac{B}{2} - 1 ,

其中 SS 是面积,II 是多边形内部的整点数,BB 是多边形边界上的整点数。 而我们要求的正好就是 II,所以只要算出 SS 与 BB:

I=S−B2+1.I = S - \frac{B}{2} + 1 .

面积是 O(1)O(1)。 三个顶点里有一个是原点,鞋带公式只剩一项:

2S=∣n⋅0+m⋅p+0⋅m−(0⋅m+p⋅0+n⋅0)∣=pm.2S = \bigl|n\cdot 0 + m\cdot p + 0\cdot m - (0\cdot m + p\cdot 0 + n\cdot 0)\bigr| = pm .

因为 m>0, p>0m>0,\ p>0,绝对值可以直接去掉。

边界整点数只需三次 gcd。 端点为整点的线段 (x1,y1)→(x2,y2)(x_1,y_1)\to(x_2,y_2) 上, 整点个数为 gcd⁡(∣Δx∣,∣Δy∣)+1\gcd(|\Delta x|,|\Delta y|)+1。三条边分别是:

边 整点个数(含两端) 举例(n=2,m=2,p=4n=2,m=2,p=4)
O→AO\to A gcd⁡(n,m)+1\gcd(n,m)+1 (0,0),(1,1),(2,2)(0,0),(1,1),(2,2)
A→CA\to C $\gcd( p-n
C→OC\to O p+1p+1 (0,0),(1,0),…,(4,0)(0,0),(1,0),\dots,(4,0)

三边相加时,三个顶点各被相邻两条边数了一次,共重复 3 次,减掉:

B=gcd⁡(n,m)+gcd⁡(∣p−n∣,m)+p.B = \gcd(n,m) + \gcd(|p-n|,m) + p .

以测试数据 fence91(n=2,m=2,p=4n=2,m=2,p=4)为例:gcd⁡(2,2)=2\gcd(2,2)=2,gcd⁡(∣4−2∣,2)=2\gcd(|4-2|,2)=2,p=4p=4, 故 B=2+2+4=8B = 2+2+4 = 8,与上表「3+3+5−3=83+3+5-3=8」一致。

合成最终公式

把 2S2S 与 BB 代回,两边乘 2 以避开小数:

I=2S−B+22=pm−gcd⁡(n,m)−gcd⁡(∣p−n∣,m)−p+22.I = \frac{2S - B + 2}{2} = \frac{pm - \gcd(n,m) - \gcd(|p-n|,m) - p + 2}{2} .

Pick 定理保证分子必为偶数,所以直接用整数除法 // 就是精确答案。

下面画出 n=2,m=2,p=4n=2,m=2,p=4 的格点图,○ 是边上的整点、● 是内部整点、· 是外部: 可以看到边界恰好 8 个点,内部只有 (2,1)(2,1) 一个。

text
y=2  · · ○ · ·
y=1  · ○ ● ○ ·
y=0  ○ ○ ○ ○ ○
     x0 x1 x2 x3 x4

代入公式验证:2S=82S=8,B=8B=8,I=(8−8+2)/2=1I=(8-8+2)/2=1,与图中唯一的 ● 吻合。 这张图把 SS、BB、II 三量的关系完整展示了一遍:面积减去边界贡献再补回常数 1, 剩下的恰好是内部整点数。

代码

Python 版:

python
#!/usr/bin/env python3
# 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-10-01 06:07
# update_at: 2026-10-01 06:07

import sys
from math import gcd


def interior_points(n: int, m: int, p: int) -> int:
    """返回三角形 (0,0),(n,m),(p,0) 内部(不含边界)的整点数。

    Pick 定理 I = S - B/2 + 1;这里改用两倍量避免小数:
    I = (2S - B + 2) / 2,其中 2S = p*m,B 是三条边上的整点数之和。
    """
    area2 = p * m                                          # 鞋带公式的两倍面积
    boundary = gcd(n, m) + gcd(abs(p - n), m) + p           # 三条边的 gcd 之和
    return (area2 - boundary + 2) // 2


def solve() -> None:
    n, m, p = map(int, sys.stdin.buffer.read().split())
    print(interior_points(n, m, p))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

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-10-06 10:30
 * update_at: 2026-10-06 10:30
 */

// usaco-3.4.3 fence9:数三角形 (0,0),(n,m),(p,0) 内部的整点数。
// Pick 定理 S = I + B/2 - 1,移项并用两倍量避免小数:
//   2S = p * m
//   B  = gcd(n,m) + gcd(|p-n|,m) + p   (三条边上的整点数,顶点重复计数已扣掉)
//   I  = (2S - B + 2) / 2
#include <cstdio>
using namespace std;

typedef long long ll;

ll n, m, p; // 三角形三个顶点 (0,0)、(n,m)、(p,0)

// 欧几里得算法求最大公约数,供统计边界整点数使用
ll gcd_ll(ll a, ll b) {
    while (b != 0) {
        ll t = a % b;
        a = b;
        b = t;
    }
    return a;
}

int main() {
    scanf("%lld %lld %lld", &n, &m, &p);

    ll area2 = p * m;                                      // 鞋带公式给出的两倍面积
    ll boundary = gcd_ll(n, m) + gcd_ll(p - n < 0 ? n - p : p - n, m) + p; // 三边整点数
    ll interior = (area2 - boundary + 2) / 2;              // Pick 定理求内部整点数

    printf("%lld\n", interior);
    return 0;
}

复杂度

  • 时间:O(log⁡min⁡(n,m))O(\log\min(n,m)),即三次 gcd;在本题上限下几乎是常数,实测单组约 0.018s。
  • 空间:O(1)O(1)。

用真实数据验证:12 组数据(含 n=0n=0 的退化三角形、n,pn,p 取到 30000 的大数据) 输出全部与标准答案一致,单组峰值内存约 14.7 MB。

总结

顶点全为格点的多边形,「内部整点数」不需要逐点枚举:Pick 定理 S=I+B2−1S=I+\frac{B}{2}-1 把它化归为「面积 + 边界整点数」。本题三角形一个顶点在原点、一个在 xx 轴上, 使 2S=pm2S=pm、B=gcd⁡(n,m)+gcd⁡(∣p−n∣,m)+pB=\gcd(n,m)+\gcd(|p-n|,m)+p 都退化成一行式子, 于是 O(nm)O(nm) 的枚举被压成 O(log⁡)O(\log) 的常数次 gcd。