UVa 1393 Highways (动态规划)
题目
题目大意
有一个\(n\)行\(m\)列(\(1 ≤ n, m ≤ 300\))的点阵, 问: 一共有多少条非水平非竖直的直线至少穿过其中两个点? 例如, \(n = 2\), \(m = 4\)时答案为\(12\), \(n = m = 3\)时答案为\(14\)。
题解
一开始看到题目我立马想到了\(SPOJ 104 Highways\)(基尔霍夫矩阵-树定理), 然而本题跟这个定理完全没有关系。
首先考虑如何判断是否形成一条之前没有出现过的直线, 如果它向量两坐标的最大公约数为\(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\)即可。
代码
#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;
}