HDU6053 TrickGCD

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

定义g(d)为gcd(i,j)%d==0的数量,对于一个位置a[x]有a[x]/d(向下取整)种,那么g(d)= ∏ ( a [ x ] / d ) \prod(a[x]/d) (a[x]/d),d的范围是[2,min(a)]

而最后的答案应该是g(2)+g(3)+0*g(4)+g(5)-g(6)+g(7)+0*g(8)+0*g(9)…
为什么4的系数是0呢?因为g(4)在g(2)已经算了,g(8)g(9)都是同理。
为什么g(6)的系数是-1?因为g(2)中算过一次,g(3)中也算过一次,那么就多了一次,所以要减一次。

这真好满足了mu函数的规律,只是符号正好反了,所以ans= ∑ \sum d=2:min(a) − m u [ d ] -mu[d] mu[d] ∏ \prod i=1:n ( a [ i ] / d ) (a[i]/d) (a[i]/d)

直接做的复杂度min(a)*n,是1e10级的,显然不可以。
考虑如何优化:
换一种存法,把原来a数组的值当作下标,记录一下那个值出现了多少次,然后做一个前缀和。

这样公式就变成了ans= ∑ \sum d=2:min(a) − m u [ d ] -mu[d] mu[d]* ∏ i = 1 m a x ( a ) / d i 在 d 下 的 次 数 \prod_{i=1}^{max(a)/d}i^{在d下的次数} i=1max(a)/did

在d下的次数的意思是原a数组中a[x]/d==i的个数。枚举的时候,当枚举到d和i时,容易反推出[di,d(i+1)-1]这个区间里面的任意一个值向下整除d都等于i,所以这个区间的个数和就是i的次数了,注意区间的右端点不能超出原a数组中最大值。前缀和的意义也就是为了O1地获得这个次数,对于次数容易想到使用快速幂来优化。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N=1e5+5;
const int maxn=1e5;
const ll mod=1e9+7;

int mu[N],prime[N];
bool vis[N];
void work_mobius()
{
    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]]=true;
            if(i%prime[j]) mu[i*prime[j]]=-mu[i];
            else
            {
                mu[i*prime[j]]=0;
                break;
            }
        }
    }
}

ll a[N];

ll qpow(ll a,ll b){
    ll ret=1;
    while(b){
        if(b&1)ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}

int main(){
    work_mobius();
    int t;
    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++){
        memset(a,0,sizeof(a));
        int n;
        scanf("%d",&n);
        ll ma=0,mi=N;
        for(int i=1,x;i<=n;i++){
            scanf("%d",&x);
            ma=max(ma,1ll*x);
            mi=min(mi,1ll*x);
            a[x]++;
        }
        for(int i=2;i<=ma;i++)a[i]=a[i-1]+a[i];
        ll ans=0;
        for(int i=2;i<=mi;i++){//感性地体会下当d越大,内层的循环次数越少
            if(mu[i]==0)continue;//mu[i]==0的话,贡献肯定时0,没必要算,减少了很多运算
            ll mm=1ll*-mu[i];
            int ed=ma/i;
            for(int j=1;j<=ed;j++){
                ll l=i*j,r=min(ma,1ll*(i*j+i-1));
                mm=mm*qpow(1ll*j,a[r]-a[l-1])%mod;
            }
            ans=(ans+mm+mod)%mod;
        }
        printf("Case #%d: %lld\n",ca,ans);
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值