题目描述
给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个。注意三角形的三点不能共线。
输入
输入两个正整数n和m,分别表示行数和列数。
输出
输出一个正整数,为所求三角形数量。
示例
输入:2 2
输出:76
解题思路
直接计算三角形个数(不共线三个点的所有可能性)有一定难度,所以可以反向思考,通过计算任取三个点的所有可能性,再减去所有共线的情况,结果就是不共线的所有情况,即所求三角形数量。
任取三个点
这里是排列组合中,组合 的基本定义:
从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。用符号 C(n,m) 表示。(源自百度百科)
对于 n×m 个网格,其网格点有 (n+1) × (m+1) 个。任取三个点的组合有 C((n+1) × (m+1), 3) 个。
共线
共线的情况可以分为两大类:
- 在同一行共线 / 在同一列共线
- 在对角线上共线
同一行上共线
每一行有(m+1)个网格点,在(m+1)个网格点任取三个点,即 C((m+1), 3)。
一共有(n+1)行,故再乘以行数 C((m+1), 3) × (n+1)。
同一列上共线
每一列有(n+1)个网格点,在(n+1)个网格点任取三个点,即 C((n+1), 3)。
一共有(m+1)列,故再乘以列数 C((n+1), 3) × (m+1)。
对角线上共线
首先需要了解gcd(最大公约数)算法:GCD算法(最大公约数算法)解析
假设线的一个端点固定在整个网格的左上角,即该端点坐标为(1,1),枚举其他所有的点(除去同一行/列上共线的点);
端点(1,1)与任意一点(x,y)((x,y)与(1,1)不在同一行/列上)的连线之间的点的数量为:
(gcd(i,j)-1) (1<=i<=n,1<=j<=m,i=x-1,j=y-1)
即有 (gcd(i,j)-1) 种三点共线的可能性。(请各位自己证明…)
以上结果,是基于端点在(1,1)这种情况。我们可以将 三点共线的线段 在网格内进行 平移 / 对称翻转,得到端点在其他位置的所有情况。
平移
平移得到的线段个数 = 水平移动的最大距离 × 垂直移动的最大距离,
即 (m+1-j) × (n+1-i)。
对称翻转
这个很容易理解,只有正/反 2种 情况。
代码实现
JavaScript
function cnt(x){
if( x<3 ) return 0
return x * (x-1) * (x-2) / 6
}
function gcd(a,b){
if(b == 0) return a
else return gcd(b,a % b)
}
function triangle(n,m){ // n为行数,m为列数
var sum = cnt((n+1)*(m+1))
var row = (n+1) * cnt(m+1)
var col = (m+1) * cnt(n+1)
for(var i=1; i<=n; i++){
for(var j=1; j<=m; j++){
var temp = (gcd(i,j)-1) * (m+1-j) * (n+1-i) * 2
}
}
return sum - row - col - temp
}
console.log(triangle(2,2)) // 76