题目描述
给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个。下图为4x4的网格上的一个三角形。注意三角形的三点不能共线。
输入格式
输入一行,包含两个空格分隔的正整数m和n。
输出格式
输出一个正整数,为所求三角形数量。
emmmmmmmm, 刚看到这道题, 这岂不是组合数, 一个n * m的网格上一共有(n + 1) * (m + 1) 个节点, 那么选择的方案数显然是$C^3_{(n+ 1)*(m+ 1)}$, 又因为组成三角形, 肯定三点不能共线, 根据容斥的原理, 肯定要减去不合法的。 .当三点在横轴和竖轴上的时候, 显然就是减去$(m +1) * C^3_{n + 1}$和$(n+1) * C^3_{m+1}$, 那我们来考虑斜着的情况.
在一个坐标轴中, 一条直线的斜率可以是正的或者负的, 由于矩形的对称性, 我们只考虑斜率大于0的情况乘以2就行了。
假设我们固定了一个点, 我们可以暴力$O(n^2)$的枚举每一个点, 因为已经固定两个点, 那么与这两点之间的点与这两点就组成了一条直线,就要减去这种情况, 那么怎么求这两点之间的整数点呢???
我们可以把(x, y)分段, 显然可知, 当把(x, y)分成相同的段数时, 这条直线上的点都是整数点(那我就不证明了。。);
假设我们把(x,y)分成a段, 因为每一段对应直线上的点都是一个整点, 所以除去两端的点直线上的点就是a - 1,我们要找出最多的点, 就要找出最大的a, 因为a | x, a | y, 当a == gcd(x, y); 此时的a最大, 就包含了所有的情况。
这样你枚举固定的点, 又暴力枚举每个点, 这样的复杂度显然是不现实的, 我们假设选择的点是(0, 0)和一个点(x, y), 那么在这个矩阵上存在和这条直线平行(或重合)且长度相等的直线就有$(m - j+1) * (n - i +1)$个 (横着平移的话有$m - j +1$个, 竖着平移有$n - i +1$个), 所以我们就假设已经固定了(0, 0)点, 暴力枚举每一个点, 直接减去所有等效的直线, 最终复杂度是$O(nm)$

#include <bits/stdc++.h> using namespace std; typedef long long ll; const int INF = 0x3f3f3f3f; const int MAXN = 1e5 + 100; const int MAXM = 3e3 + 10; const double eps = 1e-5; template < typename T > inline void read(T &x) { x = 0; T ff = 1, ch = getchar(); while (!isdigit(ch)) { if(ch == '-') ff = -1; ch = getchar(); } while (isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); } x *= ff; } template < typename T > inline void write(T x) { if (x == 0) { putchar('0'); return ; } if (x < 0) putchar('-'), x = -x; static T tot = 0, ch[30]; while (x) { ch[++tot] = x % 10 + '0'; x /= 10; } while (tot) putchar(ch[tot--]); } ll n, m, ans; inline ll C(ll x) { return x * (x - 1) * (x - 2) / 6; } inline int gcd(int x, int y) { if (x == 0) return y; return gcd(y % x, x); } int main() { read(n); read(m); ans = C((n + 1) * (m + 1)); ans -= (n + 1) * C(m + 1); ans -= (m + 1) * C(n + 1); for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { ans -= 2ll * (gcd(i, j) - 1) * (m - j + 1) * (n - i + 1); } } write(ans); return 0; }