题目
错误 の の の考场思路
对于其中一个点是原点 ( 0 , 0 ) (0,0) (0,0) 的情况:
任意两个共线的点 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2) 都有:
x 1 ( x 1 , y 1 ) = x 2 ( x 2 , y 2 ) ∧ y 1 ( x 1 , y 1 ) = y 2 ( x 2 , y 2 ) \frac{x_1}{(x_1,y_1)}=\frac{x_2}{(x_2,y_2)}\wedge \frac{y_1}{(x_1,y_1)}=\frac{y_2}{(x_2,y_2)} (x1,y1)x1=(x2,y2)x2∧(x1,y1)y1=(x2,y2)y2
然后我还想到了皮克定理 。
百度百科:皮克定理是指一个计算点阵中顶点在格点上的多边形面积公式,该公式可以表示为 S = a + b 2 − 1 S=a+\frac{b}{2}-1 S=a+2b−1其中 a a a 表示多边形内部的点数, b b b 表示多边形边界上的点数, S S S 表示多边形的面积。
正确の题解思路
膜拜大佬 o r z o r z o r z \tt{orz\; orz\; orz} orzorzorz。
首先,用一个矩形将一个三角形“包住”。也就是说,找到一个最小的矩形,使得三角形完全在矩形内。
图中灰色的是三角形,外面的就是包住它的矩形。因为一个三角形只有一个“包围矩阵”,所以 不同的包围矩阵一定对应不同的三角形。而且 包围矩阵一定是原矩阵的子矩阵。
子矩阵也有
O
(
n
2
m
2
)
\mathcal O(n^2m^2)
O(n2m2) 个啊,怎么搞?发现 长的一样的包围矩阵对应相同数量的三角形。所以只需要枚举包围矩阵的长和宽即可。个数是很好计算的。至少比这道题本身简单多了。枚举包围矩阵的复杂度降为
O
(
n
m
)
\mathcal O(nm)
O(nm) 。
因为包围矩阵是最小的可以包围三角形的矩形,所以 包围矩形的每条边上都有一个三角形的顶点。一个三角形只有三个顶点,所以说三角形的某个顶点一定抵住了两条边,也就是说,三角形至少有一个顶点在包围矩阵的顶点上。
我们先假设包围矩阵的长和宽(指 格子 的数量,也就是说,有 n + 1 n+1 n+1 个格点)为 n n n 和 m m m 。接下来可以分类讨论:
情况一:三角形只有一个顶点在矩形顶点上
已经在矩形顶点上的点有四种情况(东南、东北、西南、西北),剩下两个点一个在长边上、一个在宽边上(并且不在顶点上),乘法原理 4 ( n − 1 ) ( m − 1 ) = 4 n m − 4 m − 4 n + 4 4(n-1)(m-1)=4nm-4m-4n+4 4(n−1)(m−1)=4nm−4m−4n+4 。
情况二:三角形有两个顶点在矩形顶点上
类似的,我可以先固定一个点,假设它是左下角。接下来又得分类讨论了:
0x01 右下角
如图,第三个点一定在最顶上的边。共
n
−
1
n-1
n−1 个。
0x02 左上角
和 右下角
情况相似(只需要将上图顺时针旋转
9
0
o
90^\text{o}
90o 即可),共
m
−
1
m-1
m−1 个。
0x03 右上角
上图的方案明显是合法的,看出此时第三个点可以在
(
n
+
1
)
(
m
+
1
)
−
4
(n+1)(m+1)-4
(n+1)(m+1)−4(减
4
4
4 是去掉矩形的顶点)的格点中随便取——除了 三点共线 的情况。线段上的格点怎么算?
将左下角视为坐标系原点 ( 0 , 0 ) (0,0) (0,0) ,则右上角(线段的另一个端点)为 ( n , m ) (n,m) (n,m) ,结论:线段上除端点外有 gcd ( n , m ) − 1 \gcd(n,m)-1 gcd(n,m)−1 个点。这里简单说明一下。
在线段上的格点 ( a , b ) (a,b) (a,b) 一定满足 b a = m n \frac{b}{a}=\frac{m}{n} ab=nm(斜率相同嘛,不考虑 a = 0 a=0 a=0 的情况)。若要 a m n \frac{am}{n} nam 为整数, a a a 必须含有 n gcd ( n , m ) \frac{n}{\gcd(n,m)} gcd(n,m)n 的因子。而 0 < a < n 0<a<n 0<a<n ,故只有 gcd ( n , m ) − 1 \gcd(n,m)-1 gcd(n,m)−1 个取值。
总结一下,一共 ( n + 1 ) ( m + 1 ) − gcd ( n , m ) + 1 (n+1)(m+1)-\gcd(n,m)+1 (n+1)(m+1)−gcd(n,m)+1 个。
0x04 总的来说
总的来说,方案数是
(
n
−
1
)
+
(
m
−
1
)
−
4
+
(
n
+
1
)
(
m
+
1
)
−
gcd
(
n
,
m
)
+
1
=
n
m
+
2
n
+
2
m
−
gcd
(
n
,
m
)
−
4
(n-1)+(m-1)-4+(n+1)(m+1)-\gcd(n,m)+1=nm+2n+2m-\gcd(n,m)-4
(n−1)+(m−1)−4+(n+1)(m+1)−gcd(n,m)+1=nm+2n+2m−gcd(n,m)−4 。
发现上面的所有情况都可以上下翻折(但是不可以左右翻折),所以带的是系数 2 2 2 。
情况二的总和,就是 2 n m + 4 n + 4 m − 2 gcd ( n , m ) − 8 2nm+4n+4m-2\gcd(n,m)-8 2nm+4n+4m−2gcd(n,m)−8 。
情况三:三角形有三个顶点在矩形顶点上
显然,只有 4 4 4 种,枚举哪个矩形顶点不重合。
综上!!!
情况一:
4
n
m
−
4
n
−
4
m
+
4
4nm-4n-4m+4
4nm−4n−4m+4
情况二:
2
n
m
+
4
n
+
4
m
−
2
gcd
(
n
,
m
)
−
8
2nm+4n+4m-2\gcd(n,m)-8
2nm+4n+4m−2gcd(n,m)−8
情况三:
4
4
4
总和:
6
n
m
−
2
gcd
(
n
,
m
)
6nm-2\gcd(n,m)
6nm−2gcd(n,m)
搞定了!
代码
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline int readint(){
int a = 0, f = 1; char c = getchar();
for(; c<'0' or c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c and c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
void writeint(long long x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar(x%10+'0');
}
inline int getGcd(int a,int b){
while(b) a %= b, swap(a,b);
return a;
}
struct Point{
int x, y;
Point(int X=0,int Y=0):x(X),y(Y){}
long long operator*(const Point &that)const{
return x*that.y-y*that.x;
}
};
int n, m;
/** @brief 用来骗分的暴力代码,这里就一并给出了 */
void work1(){ // 暴力大法好!!!
long long ans = 0;
for(int x1=0; x1<=n; ++x1)
for(int y1=0; y1<=m; ++y1)
for(int x2=0; x2<=n; ++x2)
for(int y2=0; y2<=m; ++y2)
for(int x3=0; x3<=n; ++x3)
for(int y3=0; y3<=m; ++y3)
if(Point(x2-x1,y2-y1)*Point(x3-x1,y3-y1) != 0)
++ ans;
writeint(ans/6);
putchar('\n');
}
int main(){
// freopen("triangle.in","r",stdin);
// freopen("triangle.out","w",stdout);
n = readint(), m = readint();
if(n <= 10 and m <= 10){
work1();
return 0;
}
long long ans = 0;
for(int i=1; i<=n; ++i) // 矩形的个数是(n-i+1)(m-j+1)
for(int j=1; j<=m; ++j)
ans += 1ll*(n-i+1)*(m-j+1)*(6*i*j-2*getGcd(i,j));
writeint(ans);
putchar('\n');
return 0;
}