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

12 篇文章 0 订阅
6 篇文章 0 订阅

题目大意:

给你一个数组 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<bits/stdc++.h>
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; i<maxn; i++)
    {
        if(!vis[i])
        {
            prime[cnt++] = i;
            mu[i] = -1;
        }
        for(int j=0; j<cnt&&i*prime[j]<maxn; j++)
        {
            vis[i*prime[j]] = 1;
            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;i<n;i++)
        {
            scanf("%lld",&a[i]);
            num[a[i]]++;
            if(a[i]<m)m=a[i];
        }
        for(int i=1;i<maxn;i++)
        {
            num[i]+=num[i-1];
        }
        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));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值