HDU 6053 TrickGCD 莫比乌斯反演||筛法

传送门:HDU6053

题意:给定一个序列Ai,构造一个序列Bi,满足Bi<=Ai并且gcd(Bi)>=2.  问有多少种Bi序列

正解:比赛的时候有点容斥的思路,但是感觉容斥太麻烦了,就换了题去搞,完全忘了莫比乌斯反演。。


显然我们要枚举gcd,然后对于每个gcd求所有(Ai/gcd)的乘积,最后再加起来,但是这个过程会有重复的,

需要容斥,用莫比乌斯函数即可,好像也有用类似于筛法一样的容斥也能过。


搜的网上题解大多数用莫比乌斯函数的都是在函数前面加一个负号,我不是太懂为什么,但是自己举了几个小例子也是满足这个负号的,后来看到一篇博客是直接套用的书上给的莫比乌斯反演公式:


定义f[n]表示n是最大公约数情况下的计数,F[n]为n是公约数情况下的计数。

这样∑f[i](2<=i<=min(a[i]))就是要求的结果。

这样我写就明白多了嘛。。但还是希望能有大佬给解释一下为什么直接-μ(n)*F[n]也是可以的。


本题的难点还有一个就是如何快速求得(Ai/gcd)的乘积,我们可以维护一个前缀和数组b[],b[i]表示有多少Ai<=i,

然后我们类似筛法一样的枚举,假设当前枚举的gcd为d,那么j * d - 1 到 (j + 1) * d - 1 除以d的商都是j,结合b[]数组

我们就可以快速求出除以d商为j的Ai有几个了,这样复杂度和筛法差不多,nlogn数量级。


我看网上很多dalao用纯筛法的思路进行计数和容斥,也是很强的思路。


最后就是这是第一次写求莫比乌斯函数的代码,根据书上的定义写了很长,结果看网上大佬3行就解决了。。


代码:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fi first
#define se second
#define pi acos(-1)
#define inf 0x3f3f3f3f
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define rep(i,x,n) for(int i=x;i<n;i++)
#define per(i,n,x) for(int i=n;i>=x;i--)
using namespace std;
const int mod = 1e9 + 7;
typedef pair<int,int>P;
const int MAXN=100010;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int f[MAXN];//f为moebius函数值 
ll F[MAXN];
/*
void mobius(ll mn)  //大佬代码,mu[]即为moebius值 
{  
    mu[1]=1;  
    for(ll i=1;i<=mn;i++){  
        for(ll j=i+i;j<=mn;j+=i){  
            mu[j]-=mu[i];  
        }  
    }  
}  
*/
void moebius()
{
    f[1] = 1;
    for(int i = 2; i < MAXN; i++)
    {
        if(f[i] == 0)
        {
            for(int j = i; j < MAXN; j += i)
            f[j]++;
        }
    }
    for(int i = 2; i < MAXN; i++)
    if(f[i] & 1)
    f[i] = -1;
    else
    f[i] = 1;
    for(int i = 2; i * i < MAXN; i++)
    {
        for(int j = i * i; j < MAXN; j += i * i)
        f[j] = 0;
    }
}
int a[MAXN];
int b[MAXN * 2];//前缀和数组 
ll qmul(int a, int b)
{
    ll ans = 1, t = a;
    while(b)
    {
        if(b & 1) ans *= t;
        t *= t;
        b >>= 1;
        ans %= mod;
        t %= mod;
    }
    return ans;
}
int main()
{
    int T;
    moebius();
    //for(int i = 1; i <= 20; i++)
    //cout << f[i] <<" ";
    cin >> T;
    for(int kase = 1; kase <= T; kase++)
    {
        int n;
        scanf("%d", &n);
        memset(b, 0, sizeof(b));
        for(int i = 0; i < n; i++) scanf("%d", a + i), b[a[i]]++;
        for(int i = 0; i < 2 * MAXN; i++) b[i] += b[i - 1];
        int up = *min_element(a, a + n);
        ll ans = 0;
        for(int i = 2; i <= up; i++)//枚举gcd 
        {
            ll num = 1;
            for(int j = 1; j * i <= MAXN; j++)
            {
                num *= qmul(j, b[(j + 1) * i - 1] - b[j * i - 1]);
                num %= mod;
            }
            F[i] = num;
            //ans -= f[i] * num;
            //ans = (ans % mod + mod) % mod;
        }
        for(int i = 2; i <= up; i++)
        {
            ll num = 1;
            for(int j = 1; j * i <= MAXN; j++)
            ans += f[j] * F[i * j], ans %= mod;
            //ans -= f[i] * num;
            //ans = (ans % mod + mod) % mod;
            //cout << ans << endl;
        }
        printf("Case #%d: %lld\n", kase, ans);
    }
     return 0;
}


定理:是定义在非负整数集合上的两个函数,并且满足条件,那么我们得到结论

 

     

另一种描述:

 

 

在上面的公式中有一个函数,它的定义如下:

 

    (1)若,那么

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

    (3)其它情况下

 

 

对于函数,它有如下的常见性质:

 

    (1)对任意正整数

  

                            

 

        (2)对任意正整数

 

         

转载自: 点击打开链接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值