HDU 6053 TrickGCD

来源:互联网 发布:求生之路2不用网络 编辑:程序博客网 时间:2024/05/20 04:46

题目链接: 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);    }}
原创粉丝点击