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;
}
posted @ 2020-11-16 21:11  When_C  阅读(225)  评论(0编辑  收藏  举报