题目
题目大意
有一个\(n\)行\(m\)列(\(1 ≤ n, m
≤ 300\))的点阵, 问:
一共有多少条非水平非竖直的直线至少穿过其中两个点? 例如, \(n = 2\), \(m =
4\)时答案为\(12\), \(n = m = 3\)时答案为\(14\)。
题解
首先考虑如何判断是否形成一条之前没有出现过的直线,
如果它向量两坐标的最大公约数为\(1\)则没有出现过。我们考虑用\(dp_{i\ j}\)表示表示向量\((x, y)\)(\(x ≤
i\), \(y ≤ j\))共有多少个\((x, y) = 1\)的, 递推式为:
\[dp_{i\ j} = dp_{i - 1\ j} + dp_{i\ j -
1} - dp_{i - 1\ j - 1} ((i, j) ≠ 1)\]
\[dp_{i\ j} = dp_{i - 1\ j} + dp_{i\ j -
1} - dp_{i - 1\ j - 1} + 1 ((i, j) = 1)\]
接下来处理所有点, 到\((i, j)\)时,
内部各点与他组成的向量范围都在\(\{(x, y)|x∈[1,
i), y∈[1, j), x, y∈N^*\}\)内, 那么可以用\(dp_{i - 1\ j - 1}\)表示其中有多少与其互质,
再减去重复计算的(\(dp_{\frac{i - 1}{2}\
\frac{j - 1}{2}}\))。为了方便我们将所有\(i\), \(j\)增加\(1\), 最后减\(1\)即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <cstdio> long long ans[310][310], dp[310][310]; int n, m; inline long long GreatestCommonDivisor(const long long&, const long long&); int main(int argc, char const *argv[]) { for (register long long i(1); i <= 300ll; ++i) { for (register long long j(1); j <= 300ll; ++j) { dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + (GreatestCommonDivisor(i, j) == 1); ans[i][j] = ans[i - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1] + dp[i][j] - dp[i >> 1][j >> 1]; } } while (~scanf("%d %d", &n, &m) && (n || m)) { printf("%lld\n", ans[n - 1][m - 1] << 1); } return 0; } inline long long GreatestCommonDivisor(const long long &a, const long long &b) { return b ? GreatestCommonDivisor(b, a % b) : a; }
|