首页 > 代码库 > hdu6053(莫比乌斯+容斥+分块)

hdu6053(莫比乌斯+容斥+分块)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6053

 

题意: 给出一个含 n 个元素的 a 数组, 求 bi <= ai 且 gcd(b1, ..., bn) >= 2 的 b 数组的数目;

 

思路: 首先想到的方法是枚举 gcd, 对于每个 gcd x 的情况, 将所有 bi / x 连乘, 然后将所有 gcd 的情况累加一下就能的到答案了 .

然而其时间复杂度为 O(t * min(a) * n), 铁定 tle;

对于后面连乘部分是可以优化一下的 . 把 a 数组元素装到一个桶里面, 然后枚举 gcd 时按照 ai / gcd 的值分下块,

对于区间 [gcd * k, gcd * (k + 1 ) - 1], 显然区间内的值除 gcd 的商都为 k, 若 a 中有 cnt 个元素处于该区间, 那么该块内的 b 数组元素有 k^cnt 种选择方案,

再将不同块的方案数乘起来就是当前 gcd 对答案的贡献了 .

然而上面还会出现重复计算(个人感觉这个有点难理解):

对于不能分解成不同素数乘积形式的 gcd, 在第一次计算其中多次出现的素因子作 gcd 时已经计算过了, 所以不累加到答案上 .

对于能分解成不同素数乘积形式的 gcd, 其中重复计算的我们可以用容斥去重, 奇加偶减 .

可以发现这和 moblus 函数很相近, 对与不能分解成不同素数乘积形式的数, 其 mu 值为 0, 对于能分解成不同素数乘积形式的数, 若其长度为奇数, mu 值为 -1, 长度为偶数则 mu 值为 1, 恰好与容斥的奇加偶减相反, 所以给 mu 加个负号即可 .

 

代码:

技术分享
 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 4 #define ll long long
 5 using namespace std;
 6 
 7 const int mod = 1e9 + 7;
 8 const int MAXN = 1e5 + 10;
 9 
10 bool check[MAXN];
11 int prime[MAXN], mu[MAXN], vis[MAXN << 1];
12 
13 void Moblus(void){
14     memset(check, false, sizeof(check));
15     int tot = 0;
16     mu[1] = 1;
17     for(int i = 2; i < MAXN; i++){
18         if(!check[i]){
19             prime[tot++] = i;
20             mu[i] = -1;
21         }
22         for(int j = 0; j < tot && i * prime[j] < MAXN; j++){
23             check[i * prime[j]] = true;
24             if(i % prime[j] == 0){
25                 mu[i * prime[j]] = 0;
26                 break;
27             }else mu[i * prime[j]] = -mu[i];
28         }
29     }
30 }
31 
32 ll get_pow(ll x, int n){
33     ll ans = 1;
34     while(n){
35         if(n & 1) ans = ans * x % mod;
36         x = x * x % mod;
37         n >>= 1;
38     }
39     return ans;
40 }
41 
42 int main(void){
43     Moblus();
44     int t, n, x;
45     scanf("%d", &t);
46     for(int cas = 1; cas <= t; cas++){
47         memset(vis, 0, sizeof(vis));
48         int mi = MAXN, mx = - MAXN;
49         scanf("%d", &n);
50         for(int i = 0; i < n; i++){
51             scanf("%d", &x);
52             vis[x] += 1;
53             mi = min(mi, x);
54             mx = max(mx, x);
55         }
56         for(int i = 1; i < (MAXN << 1); i++){//注意范围
57             vis[i] += vis[i - 1];
58         }
59         ll ans = 0;
60         for(int i = 2; i <= mi; i++){
61             ll cnt = 1;
62             if(mu[i]){
63                 for(int j = i; j <= mx; j += i){
64                     cnt = (cnt * get_pow((ll)(j / i), vis[j + i - 1] - vis[j - 1])) % mod;
65                 }
66             }
67             ans = (ans - cnt * mu[i] + mod) % mod; 
68         }
69         printf("Case #%d: %lld\n", cas, ans);
70     }
71     return 0;
72 }
View Code

 

hdu6053(莫比乌斯+容斥+分块)