hdu 5514 Frogs 欧拉函数

12 篇文章 0 订阅
2 篇文章 0 订阅

题目链接


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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值