组合数 线段中整点个数 容斥原理 T3 todo #2240. 「CQOI2014」数三角形 - 题目 - LibreOJ (loj.ac)

题目描述

给定一个  的网格,请计算三点都在格点上的三角形共有多少个。下图为  的网格上的一个三角形。

注意:三角形的三点不能共线。

输入格式

输入一行,包含两个空格分隔的正整数  和 

输出格式

输出一个正整数,为所求三角形数量。

样例

输入

2 2

输出

76

数据范围与提示

对于所有数据,

思路

给一个 的网格, 像这样三点都在点上的三角形有几个?image.png 样例是 的网格: 依然是先选定一个点之后再看其他的点, 的网格中一共有 个点, 而我们选其中 个点, 共有 。 但显然答案是 个, 也就是说我们需要找到无解情况并将其减去。image.png像这样的情况就是无解情况, 那么再减去行的 个, 和列的 个, 结果就是 个。依然很多, 说明这种情况没算上:image.png 算上之后再减去 就是正确答案

若直接整体做, 第一个点有 种选法, 第二个点只需要与第一个点不同就行, 有 种选法, 但第三个点就不好确定, 前两种情况的选法不同, 第三种也不同。

用另外一种思想, 即容斥原理, 或称补集思想, 先求出总的方案数, 然后减去无解的方案数即可。

给一个含有 个点的矩形, 总选择方案数为 , 而不合法的方案是三个点在一个直线上时, 可以启发地思考, 在矩形左下角建立坐标系, 无解情况的斜率有一下几种:

  • , 在一行中选 个点, 共有 行:
  • , 在一列中选 个点, 共有 列:
  • , 找一种枚举方案, 先找最左下的点, 此时根据第一个点确定的情况, 会把所有方案分成 类, 再分别求每一类的情况, 那么另外两点一定在 点上方:image.png 接着再去枚举右上角的点, 有 种选择。image.png 此时 点已经确定, 点只能在由 两点确定的线段上选一个点, 故我们需要求 两点间有多少个整点, 即线段整数点数问题。image.png 则整数点数量为: , 若不包含 端点则再减一。 证明:image.png 对于整数点 因为是在 线段 上, 故 线段 的斜率和 线段 的斜率相等, 则在求 点时, 需要在满足 的情况下划分, 也就是说, 若划分了 分, 每份的相对与点的坐标是 , 且需要满足划分后的点为整数, 还要使划分的份数 最多, 故只有 满足要求。

那么最终结果就是 。 而对与 , 因为从矩形中线轴对称后, 所有 的直线都对应一个 的直线, 故 的方案数相同, 直接加两倍就行了。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>
using namespace std;
const int N = 1e3 + 10;
typedef long long LL;
 
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}
 
LL C(int n)
{
    return (LL)n * (n - 1) * (n - 2) / 6;
}
 
int main()
{
    int n, m;
    cin >> n >> m;
    n++, m++;
    LL res = C(n * m) - (LL)n * C(m) - (LL)m * C(n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            res -= 2LL * (gcd(i, j) - 1) * (n - i) * (m - j);
    cout << res << endl;
 
    return 0;
}