Luogu3166 [CQOI2014]数三角形(排列组合)题解
结论题
先给结论吧:
一个两直角边分别为\(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\times \sum_{i=1}^{n}\sum_{j=1}^{m}{(n-i)\times(m-j)\times (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\n", Ans - f * 2);
return 0;
}