结论题
先给结论吧:
一个两直角边分别为(x),(y)的格点直角三角形(即在网格图上),其斜边上的整点数量为(gcd(x,y)+1)(包含两端点).
貌似看完结论还是不会呢
思路
首先声明:本题是格点图,所以要把行数列数都加上(1)。
先简单容斥一下,合法三角形数量(=)从格点图上任选三个点的方案数(-)选出的三点共线的方案数。
-
任选三点:(C(n*m,3))
-
三点共线:
-
横线:共有(n)行,每行都是从(m)个数中选(3)个,即为(C(m,3)*n);
-
竖线:共有(m)列,每列都是从(n)个数中选(3)个,即为(C(n,3)*m);
-
斜线:斜率为正的斜线与斜率为负的斜线是等价的,故放在一起考虑。根据先前的结论,我们可以枚举两点之间行与列的差值(i)与(j),那么第三个点就可以选择前两个点之间连线上的任意一个整点,即有(gcd(i,j)-1)种方案(点与点不能重合)。所以斜线的方案总数为:
[2 imes sum_{i=1}^{n}sum_{j=1}^{m}{(n-i) imes(m-j) imes (gcd(i,j)-1)} ]
-
最后作差即可。
Tips
这里求组合数需要高效的(O(n))算法,即:
(C(n,m)=C(n,m-1)*(n-m+1)/m)
边界条件:(C(n,0)=1)。(注意要先乘后除)
事实上,本题还有(O(n))的算法,有兴趣的同学可以自行了解。
参考代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#define LL long long
using namespace std;
int n,m;
LL Ans;
LL C(int x, int y){
if(y > x) return 0;
if(y == 0 || y == x) return 1;
if(y == 1) return x;
if(y > x - y) y = x - y;
return (LL)(x - y + 1) * C(x, y - 1) / y;
}
int gcd(int x, int y){
if(y == 0) return x;
return gcd(y, x % y);
}
int main(){
scanf("%d%d", &n, &m); n += 1, m += 1;
Ans = C(m * n, 3);
Ans -= C(m, 3) * n + C(n, 3) * m;
LL f = 0;
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= m; ++ j){
f += (n - i) * (m - j) * (gcd(i, j) - 1);
}
printf("%lld
", Ans - f * 2);
return 0;
}