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

来源:互联网 发布:红警3 for mac版 编辑:程序博客网 时间:2024/04/29 17:02

题目链接


题意:

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.csdn.net/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 100010typedef 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.csdn.net/acdreamers/article/details/8542292 莫比乌斯反演——ACdreamer

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

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



碎碎念:

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

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

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

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

阅读全文
0 0
原创粉丝点击