题目描述
见 洛谷P1447。
题解
观察样例,对于每一个(x, y),如果
x/y
可以约分,那么(0, 0)到(x, y)的连线上必然有其他点,那么考虑,把它缩小多少倍可以使其最简?显然是gcd(x, y)(看似傻瓜的设问,实际是为了引起你的思考)。
那么便知道了,(0, 0)到(x, y)的线段上有gcd(x, y)个点(包括(x, y))。
于是很快可以出一个暴力解法:
long long Solve()
{
long long ans = 0;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
ans += gcd(i, j);
return ans * 2 - m * n;
}
注意,ans并不是最终答案,而是所有连线上的点的总数。初中数学不解释。
复杂度为
O(nm×O(gcd))
。原谅我并不知道gcd的复杂度。
根据复杂度分析与本地测试,这样可以拿80分。
但是其实感官上看,nm那么大,应该用什么东西把它归纳起来。我一开始考虑用每一条直线,然而并没有成功,于是改用gcd。
设
f(i)
为gcd(x, y)为i的点的总个数,那么结果就是:
(2×∑min(n,m)i=1f(i)×i)−mn
.
直接这么处理会中间值爆long long,所以答案要这样:
∑min(n,m)i=1(2(i−1)+1)×f(i)
=∑min(n,m)i=1(2i−1)×f(i)
至于算
f(i)
,可以先算出含公约数(暂不用最大)
i
的点数:
然后减去所有
f(j)j<min(n),jmodi=0
正因为
j>i
所以需要从高到底计算
f(i)
。
复杂度:
O(min(n,m)logmin(n,m))
于是可以拿到满分。
完整源代码
与上文不同,这里的ans就是最终答案。
#include<iostream>
using namespace std;
const int maxn = 100000 + 5;
typedef long long ll;
int n, m;
ll f[maxn] = {0};
ll Solve()
{
ll ans = 0;
for(int i = n; i > 0; --i) {
f[i] = ((ll)n / i) * ((ll)m / i);
for(int j = i << 1; j <= n; j += i) f[i] -= f[j];
ans += ((i << 1) - 1) * f[i];
}
return ans;
}
int main()
{
cin >> n >> m;
if(n > m) int t = n, n = m, m = t;
cout << Solve() << endl;
return 0;
}