HDU 6053 TrickGCD

4 篇文章 0 订阅

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

题意:

给出一个长度为N的序列A,让你求有同长度的满足下列要求的序列B一共有多少个。
* 对于每个Bi,满足1≤Bi≤Ai
* 对于每对( l , r ) (1≤l≤r≤n) , gcd(bl,bl+1…br)≥2
分析:
一开始我就想跟素数有关,所以我预处理了1e5内的素数表。
先比如对于一个数p(p不一定是素数),求gcd为p的倍数的答案,方法如下。
计算a数组中大小在k * p 到( k + 1 ) * p-1之间的数的个数( 1<=k<=mx/p,mx是a数组最大的数 ) 记为num,num就是所有ai / p ==k 的数的数量, 对于p来说就是可以变成 p ~ k * p 的数的个数 , 就是说把 k^num次乘到p的答案上。
举个例子, 比如有3个数 2 6 7,对于gcd是2的倍数的答案,
对于2 ~3 区间上有1个 ,有1个数可以是2
对于6~7 区间上有2个,有2个数 可以是2,4,6。
答案就是1 * 3 * 3 也就是 1 *(3^(2) )
计算gcd为pp的倍数的 满足要求所得的答案,就是下面的work函数。

ll work(ll pp)
{
    ll x=max(mn/pp,1LL)*pp,sm=1;//mn是a数组的最小值。x是满足可以作为gcd的 pp的倍数 的最小值
        while(x<=mx&&sm)
        {
            sm*=qpow(x/pp,(mp[x+pp-1]-mp[x-1]));//qpow是快速幂 求(x/pp)^(mp[x+pp-1]-mp[x-1]) 然后乘到答案上。
            x+=pp;//x就是pp的倍数。
            sm%=mod;
        }
    return sm;
}

最初我只考虑了gcd为质数倍数的答案的和,
然而直接这样打有一个问题。就是比如你计算了gcd为2 的倍数 和3的倍数的答案。
这样你会计算多一个gcd为6的倍数的答案。
所以这里你就需要容斥一下来获得正确的答案。
想到这里,我惊讶的发现2*3*5*7*11*13*17>1e5,所以1e5内最多可以有6个不同质数相乘。
所以我开了一个vector,与处理了一下i个不同素数相乘的乘积(1<=i<=6)。
然后就直接处理答案。
我们加上奇数个素数相乘的数的倍数得到的答案,减掉偶数个素数相乘的数的倍数得到的答案。
得到的就是最终的答案。
ps:本来没准备写的来着,然而惊奇的看到我跟大佬们的做法貌似有点差异,于是记录一下。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll INF=0x3f3f3f3f,mod=1e9+7;
ll p[10005],d[100005]= {0},hx=0;//p是存素数的数组,hx是素数个数,d是素筛的辅助数组
ll a[100005],mx,mn;//a数组,a数组中最值
ll mp[200005];// mp[i] 是 a数组中小于等于i的数的个数。
ll qpow(ll aa,ll bb)//快速幂
{
    ll ans=1;
    while(bb)
    {
        if(bb%2)
            ans=(ans*aa)%mod;
        aa=(aa*aa)%mod;
        bb/=2;
    }
    return ans;
}
ll work(ll pp)//计算gcd为pp的倍数的b数组的个数
{
    ll x=max(mn/pp,1LL)*pp,sm=1;
        while(x<=mx&&sm)
        {
            sm*=qpow(x/pp,(mp[x+pp-1]-mp[x-1]));
            x+=pp;
            sm%=mod;
        }
    return sm;
}
vector<ll>v[15];//预处理k个不同素数相乘的1e5以内的数
void dfs(ll dep,ll pos,ll sm)
{
    v[dep].push_back(sm);
    for(int i=pos; i<hx; i++)
    {
        if(sm*p[i]<=100000)
            dfs(dep+1,i+1,sm*p[i]);
        else
            return ;
    }
}
int main()
{

    for(int i=2; i<=100000; i++)
    {
        if(d[i]==0)
        {
            p[hx++]=i;
        }
        for(int j=0; j<hx&&p[j]*i<=100000; j++)
        {
            d[p[j]*i]=1;
        }
    }//素数筛选
    dfs(0,0,1);
    int T;
    scanf("%d",&T);
    ll num=1;
    for(int i=1;i<=6;i++)
        sort(v[i].begin(),v[i].end());//这边之前忘记sort,后面直接break就炸了,比赛结束后调试发现原来是这边忘记sort,泪~
    while(T--)
    {
        memset(mp,0,sizeof(mp));
        ll n;
        mx=0,mn=1e5;
        scanf("%I64d",&n);
        for(int i=0; i<n; i++)
        {
            scanf("%I64d",&a[i]);
            mp[a[i]]++;
            mx=max(mx,a[i]);
            mn=min(mn,a[i]);
        }
        for(int i=1; i<=200000; i++)
        {
            mp[i]=mp[i]+mp[i-1];
            mp[i]%=mod;
        }
        ll ans=0;
        for(int i=1; i<=6; i++)
        {
            int len=v[i].size(),ff;
                ff=(i%2==1)?1:-1;
            for(int j=0; j<len; j++)
            {
                int id=v[i][j];
                ll x=max(mn/id,1LL)*id,sm=1;
                if(mp[id-1]==0)// 没有比id小的数,才可能gcd是大于等于id的数。
                {
                    ans+=ff*work(id);
                    ans%=mod;
                    ans+=mod;
                    ans%=mod;
                }
                else
                    break;
            }
        }
        printf("Case #%I64d: %I64d\n",num++,ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值