http://blog.csdn.net/qingshui23/article/details/73091006
力荐这一篇,写得实在是太棒了
题意:
有 n 只青蛙,在环形的 m 块石头上跳,每只青蛙有给定的步长 ai,问所有被青蛙经过的石头的编号的总和
思路:
首先每只青蛙跳跃 k 次后必然会回到起点,
于是有 m | (k * ai),
记 gcd(m, ai) = d, m = q1 * d, ai = q2 * d,
则有 (q1 * d) | (k * q2 * d),
故 q1 | (q2 * k), 其中 (q1, q2) = 1.
故 q1 | k.
取 k = q1 = m / d, 即为回到起点所需的最少步数.
故 可以将青蛙的步长等价为 m / k = d = gcd(m, ai).
接下来就是原Po的考虑中十分出彩的地方了,
原Po的例子举得都很好,解说也都很具体形象,我这里就来写一些粗浅的证明:
对于每一块石头 x,只考虑步长为 gcd(x, m) 的青蛙(如果存在)对其做出的贡献,这样就能够避免重复计算,
而对任意的 x 考虑 gcd(x, m), 即为考虑所有 m 的因子,
问题就转化成,对每一个 m 的因子 k, 有哪些 x 满足 gcd(x, m) = k.
记 x = d1 * k, m = d2 * k, (d1 < d2)
因为 gcd(x, m) = gcd(d1 * k, d2 * k) = k * gcd(d1, d2) = k,
所以有 gcd(d1, d2) = 1, 其中 d2 = m / k,
所以 d1 为小于 d2 且与 d2 互质的数,
显然就和欧拉函数联系上了
此外要注意的是,并不是所有 m 的因子都可行,还要再多一个判断,就是 青蛙(等效后的)步长 | 因子k,
这样才保证了编号为 k 的,以及 gcd(x, m) = k 的这些编号为 x 的石头是青蛙可达的。
接着上面的说,要求和,又对 x 求和又怎么办呢?
因为 x = d1 * k, 所以对于特定的 k, 只需要知道 d1 的和,
即 <= d2 = m / k 且与 d2 互质的数的和,
其实这也是有结论的,原Po提到了并且给予了证明
在
[1,x]
中与
x
互素的数的和为:
Phi(x)∗x2
详情请看原Po博文
这样,这道题就通了
再次力荐原Po的博文,写的真的很棒
http://blog.csdn.net/qingshui23/article/details/73091006
超级丰富详细,看得小垃圾我很是感动qwqq
还有就是一些细节上的处理,
比如说
1. 本身就有步长与 m 互质的青蛙,那么必然每块石头都会经过
2. 对于等效后的步长用 unique 函数进行去重,减少重复计算
(都是跟原Po学的qwq太感谢惹)
AC代码如下:
#include <cstdio>
#include <algorithm>
#include <iostream>
#define maxn 10010
typedef long long LL;
using namespace std;
LL n, m, a[maxn], b[maxn];
int kas;
LL gcd(LL a, LL b) {
if (!b) return a;
return gcd(b, a % b);
}
LL phi(LL x) {
LL ans = x;
for (LL i = 2; i * i <= x; ++i) {
if (x % i == 0) {
ans -= ans / i;
while (x % i == 0) x /= i;
}
}
if (x > 1) ans -= ans / x;
return ans;
}
void work() {
scanf("%lld%lld", &n, &m);
bool flag = false;
for (int i = 0; i < n; ++i) {
scanf("%lld", &a[i]);
a[i] = gcd(a[i], m);
if (a[i] == 1) flag = true;
}
if (flag) { printf("Case #%d: %lld\n", ++kas, m * (m - 1) >> 1); return; }
sort(a, a + n);
n = unique(a, a + n) - a;
int tot = 0;
for (LL i = 2; i * i <= m; ++i) {
if (i * i == m) { b[tot++] = i; break; }
if (m % i == 0) {
b[tot++] = i;
b[tot++] = m / i;
}
}
LL ans = 0;
for (int i = 0; i < tot; ++i) {
for (int j = 0; j < n; ++j) {
if (a[j] > b[i]) break;
if (b[i] % a[j] == 0) {
// printf("%lld\n", b[i]);
ans += phi(m / b[i]);
break;
}
}
}
ans = ans * m / 2;
printf("Case #%d: %lld\n", ++kas, ans);
}
int main() {
freopen("5514.in", "r", stdin);
int T;
scanf("%d", &T);
while (T--) work();
return 0;
}