2017多校联合第二场 1009题 hdu 6053 TrickGCD (超详细!!!)莫比乌斯 容斥

本文详细解析了莫比乌斯反演的原理及应用,通过具体题目讲解如何利用莫比乌斯函数解决组合计数问题,并给出了AC代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接


题意:

Problem Description
You are given an array  A  , and Zhu wants to know there are how many different array  B  satisfy the following conditions?

1BiAi
* For each pair( l , r ) ( 1lrn ) ,  gcd(bl,bl+1...br)2


思路:

题意很好理解,样例也很好做。

A 数组为 4 4 4 4 时,考虑

1) gcd = 2, 有 2 * 2 * 2 * 2 = 16 种

2) gcd = 3, 有 1 * 1 = 1 种

3) gcd = 4, 在前面考虑过了,故不再重复计算

故一共有 16 + 1 = 17 种


其实这里说的并不严谨,事实上我们讨论的并不是 gcd = x, 而是 x | gcd, 在第一类讨论 gcd = 2 时尤为明显。

事实上 2 * 2 * 2 * 2 算的是 2 = x | gcd 的种数,因为 4 4 4 4 的这种情况,其 gcd 事实上是 4 而并非 2.

所以说,我们上面的分类讨论只是十分直观浅显粗浅的分类。


不过大概意思已经出来了,对于每一个 x,去算 PI (i = 1 ~ n) (a[i] / x), 再对重复计算的部分进行一些处理,求个和,就是最后的答案。

然而这里说的一些处理还是太笼统了,我怎么知道要怎么处理呢?


我们先换个话题,来看一看莫比乌斯函数的定义。

(来源:http://blog.youkuaiyun.com/acdreamers/article/details/8542292

莫比乌斯函数定义如下:

 

    (1)若,那么

    (2)若均为互异素数,那么

    (3)其它情况下



是不是看起来有些莫名其妙?

先不管它,我们来对刚刚那道题要算的 x 举几个例子:

考虑算到 x = 4 | gcd 时的情况,因为算 x = 2 | gcd 已经包括了这种情况,所以 x = 4 时可以不用计算

考虑算到 x = 6 | gcd 时的情况,因为算 x = 2 | gcd 时包括了一次该情况,算 x = 3 | gcd 时又包括了一次该情况,所以算 x = 6 时应该减去算出来的结果

考虑算到 x = 12 | gcd 时的情况,因为算 x = 2 | gcd 时包括了一次该情况,算 x = 3 | gcd 时包括了一次该情况,算 x = 4 | gcd 时没有计算,算 x = 6 | gcd 时减去了一次重复的该情况,总共恰好算了一次,所以 x = 12 | gcd 时也可以不用计算。


大概看出了一些规律:

如果当前算的 x 是素数,那么肯定是第一次算,所以当前需要加上一次。

如果当前算的 x 是两个相异素数的乘积 x = p1 * p2,那么算 p1 时加过一次,算 p2 时加过一次,所以当前需要减去一次。

如果当前算的 x 是三个相异素数的乘积 x = p1 * p2 * p3,那么算 p1 时加过一次,算 p2 时加过一次,算 p3 时加过一次,算 p1 * p2 时减过一次,算 p2 * p3 时减过一次,算 p3 * p1 时减过一次,1 + 1 + 1 - 1 - 1 - 1 = 0,所以当前需要加上一次。

……

如果当前算的 x 是 k 个相异素数的乘积 x = p1 * p2 * ... * pk,那么在之前的计算中

算一个素数时加上过 C(k, 1) 次,算两个素数时减去过 C(k, 2) 次,...,算 m 个素数时加/减过 C(k, m) 次

总共计算过 C(k, 1) - C(k, 2) + C(k, 3) - ... + ((-1) ^ k) * C(k, k - 1) 

= (1 - 1) ^ k + C(k, 0) - ((-1) ^ (k + 1)) * C(k, k) 

= 0 + 1 - (-1) ^ (k + 1)

= 1 - (-1) ^ (k + 1)

为使总共计算次数为 1 次,当前需要弥补的计算次数为 1 - (1 - (-1) ^ (k + 1)) = (-1) ^ (k + 1).


诶嘿,是不是和上面的莫比乌斯函数靠上一点了?


继续。

由算数基本定理可知,任意的自然数 x = (p1 ^ a1) * (p2 ^ a2) * (p3 ^ a3) * ... * (pk ^ ak),

除去我们上面讨论过的情况,其余的情况就是 至少存在一个 ai 使得 ai > 1 的情况了。

假设 x = (p1 ^ a1) * (p2 ^ a2) * (p3 ^ a3) * ... * (pk ^ ak),

我们考虑对应的 x' = p1 * p2 * p3 * ... * pk,

由上面的计算过程,最终我们使得 x' | gcd 的情况总计被计算过一次,

又因为 x' | x, 所以 x | gcd 的情况迄今也总计被计算过一次,这就恰好达成了总计计算 1 次的目标。

所以,此时的计算次数为 0 次,与莫比乌斯函数的形式再次一拍即合。


所以,我们可以说,莫比乌斯函数 μ(x) 即为

算到 x 时,使得 x 总计被计算一次 的 补偿系数(雾)

(其实就是方便容斥...)


细节上有一点需要注意的,那就是 μ(x) 事实上与我们刚才说的在大小上是反的,所以是减去而不是加上,

分析到这里,我们就知道每个对应的 x 应该被计算多少次了,那就是 -μ(x) 次


(看到这里没有看懂的童鞋请再回去看几遍前面讲的 或者 再自己手算几个简单的例子啦~)


这道题的另一个注意点就是先预处理一下读入的 a[i], 毕竟如果对于每个 x 都要算 1e5 个 a[i] / x 的乘积,时间代价还是太大了。

因为有很多 a[i] / x 会落到同一个区间中,所以可以统计 a[i] 的个数,前缀和处理一下,sum[m] 表示大小为 [0, m] 的区间内有多少个数,

那么 sum[x * (i + 1) - 1] - sum[x * i - 1] 即表示 a[ ] / x  = i 的数的个数,快速幂一下,这部分的乘积就有了.


AC代码如下:

#include <bits/stdc++.h>
#define maxn 100010
typedef long long LL;
inline int min(int a, int b) { return a < b ? a : b; }
inline int max(int a, int b) { return a > b ? a : b; }
int kas, prime[maxn], cnt[maxn], mu[maxn];
bool check[maxn];
const LL mod = 1e9 + 7;
void Mobius() {
    memset(check, 0, sizeof(check));
    mu[1] = 1;
    int tot = 0;
    for (int i = 2; i <= maxn; ++i) {
        if (!check[i]) {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for (int j = 0; j < tot; ++j) {
            if (i * prime[j] > maxn) break;
            check[i * prime[j]] = true;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            else {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
}
LL poww(LL a, LL b) {
    LL ret = 1;
    while (b) {
        if (b & 1) ret = ret * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ret;
}
void work() {
    int n;
    scanf("%d", &n);
    int minn = maxn + 10, maxx = 0;
    memset(cnt, 0, sizeof(cnt));
    for (int i = 0; i < n; ++i) {
        int x;
        scanf("%d", &x);
        ++cnt[x];
        minn = min(minn, x); maxx = max(maxx, x);
    }
    for (int i = 1; i <= maxn; ++i) cnt[i] += cnt[i - 1];
//    for (int i = 1; i <= 100; ++i) printf("%d ", cnt[i]);
    LL ans = 0;
    for (int i = 2; i <= minn; ++i) {
        if (mu[i] == 0) continue;
        LL mul = 1;
        for (int j = 1; i * j <= maxx; ++j) {
            int up = min(i * (j + 1), maxx + 1);
            int num = cnt[up - 1] - cnt[i * j - 1];
            mul *= poww(j, num); mul %= mod;
        }
        ans = (ans - mu[i] * mul + mod) % mod;
//        printf("%lld\n", mul);
    }
    printf("Case #%d: %lld\n", ++kas, ans);
}
int main() {
    Mobius();
    int T;
    scanf("%d", &T);
    while (T--) work();
    return 0;
}


参考:

http://blog.youkuaiyun.com/acdreamers/article/details/8542292 莫比乌斯反演——ACdreamer

http://blog.youkuaiyun.com/u012860063/article/details/47686525 莫比乌斯反演 模板啊——tianyiming

http://blog.youkuaiyun.com/ACTerminate/article/details/76216345 hdu6053 TrickGCD [莫比乌斯函数] ——ACTerminate



碎碎念:

莫比乌斯函数...看了我两三天看得超级迷 0.0

F 和 f 什么的...现在还是很迷Orz

后来干脆直接看这道题以及题解了(摊手

不过这道题好歹还能算是...告一段落了吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值