HDU - 6053 TrickGCD(莫比乌斯反演+容斥思想+分块前缀和技巧)

题目大意:

给你一个数组 A ,问你有多少不大于 A 的数组 B 使得 B 中所有元素的最大公因数不为1。(数组 B 不大于数组 A 就等价于,对于任意 A 数组中的元素 a [ i ] 和 B 数组中对应元素 b [ i ] ,均有:a [ i ] >= b [ i ])

思路:

容斥思想:该问题就可以转化成求有多少数组 B 满足:B 中的所有元素的最大公因数为 1。

莫比乌斯反演:
设:
f(x)x
F(x)x
那么显然:

F(x)=x|df(d)

那么根据莫比乌斯反演公式可知:
f(x)=x|du(dx)f(d)

那么我们最后要求的是 f(1) ,既将 x=1 带入上式可得:
ans=i=1nu(i)f(i)

分块前缀和技巧:
显然可知:

f(x)=i=1naix

但是我们如果朴素的方法求每一个 f(x) 需要 O(n) 的时间复杂度,显然这样是承受不了的。

那么我们想办法再继续优化一下上式:

f(x)=i=0amax/xinum(i,x)

ps:这里的sum(i)表示的是: A 数组中有多少数除 x 向下取整为 i 。

那么现在我们设: sum[i] 表示 A 数组中,值在区间 [0,i] 的元素个数。
下面给出 num(i,x) 的计算公式:

num(i,x)=sum[x(i+1)1]sum[xi1]

ps: Axi=A[xi,x(i+1)1]

如此可将时间复杂度降为: O(n(logn)2)

代码:

#include
using namespace std;
#define maxn 100500
#define MOD 1000000007
#define mod(x) ((x)%MOD+MOD)%MOD
long long int a[maxn],mu[maxn],num[maxn];
int cas=1,n,m,vis[maxn],prime[maxn];

void init_mu()
{
    memset(vis,0,sizeof(vis));
    mu[1] = 1;
    int cnt = 0;
    for(int i=2; iif(!vis[i])
        {
            prime[cnt++] = i;
            mu[i] = -1;
        }
        for(int j=0; j1;
            if(i%prime[j]) mu[i*prime[j]] = -mu[i];
            else
            {
                mu[i*prime[j]] = 0;
                break;
            }
        }
    }
}
long long int fast_pow(long long int s,long long int x)
{
    long long int ans=1;
    while(x>0)
    {
        if(x&1)
        {
            ans*=s;
            ans=mod(ans);
        }
        x>>=1;
        s*=s;
        s=mod(s);
    }
    return mod(ans);
}
long long int F(int x)
{
    long long int ans=1;
    for(int i=0;i<=maxn/x;i++)
    {
        ans*=fast_pow((long long int)i,num[min(x*(i+1),maxn)-1]-num[min(x*i,maxn)-1]);
        ans=mod(ans);
    }
    return mod(ans);
}
int main()
{
    int T;
    scanf("%d",&T);
    init_mu();
    while(T--)
    {
        m=maxn<<1;
        scanf("%d",&n);
        memset(num,0,sizeof(num));
        for(int i=0;iscanf("%lld",&a[i]);
            num[a[i]]++;
            if(a[i]for(int i=1;i1];
        }
        long long int ans=0;
        for(int i=2;i<=m;i++)
        {
            if(mu[i]==0)continue;
            if(mu[i]==1)ans-=F(i);
            else ans+=F(i);
            ans=mod(ans);
        }
        ans=mod(ans);
        printf("Case #%d: %lld\n",cas++,mod(ans));
    }
}

你可能感兴趣的:(数学,数论,思维)