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 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;
}
原文地址:https://www.cnblogs.com/whenc/p/13988989.html