HDU 6053 TrickGCD(分块)

 

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

 

【题目大意】

  给出一个数列每个位置可以取到的最大值,
  问这个可以构造多少个数列,使得他们的最大公约数大于1

 

【题解】

  我们可以枚举最大公约数k,对于k来说,
  他对答案的贡献为∏[ai/k],我们将数列中的数字转化为权值数组
  ∏_{i=1}^{100000}[i/k],对于求解i/k的部分我们可以进行数值分块,
  j*k-1~j*k+k-1的数值除k得到的结果都是相同的,因此可以直接求这个结果的幂次,
  这时候只要再加一个权值数组的前缀和,问题就迎刃而解了。
  数值分块计算的复杂度为n+n/2+n/3+n/4+n/5+……+n/n=nlogn。
  对于计算结果,我们需要进行容斥,奇数次素数乘的系数为1,偶数次素数乘的系数为-1,
  对于出现素数幂的合数其系数为0,
  我们发现这个容斥恰好是莫比乌斯函数的相反数,因此我们取反即可。
  这有个小小的优化,对于系数为0的情况,我们可以直接跳过,不进行计算。

 

【代码】

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=200010;
typedef long long LL;
const LL mod=1000000007;
int T,n,a[N],b[N],cnt[N],cas=1,p[N]; 
LL ans=0;
int tot,miu[N],sum[N],v[N];
void read(int&a){
    char ch;while(!((ch=getchar())>='0')&&(ch<='9'));
    a=ch-'0';while(((ch=getchar())>='0')&&(ch<='9'))a*=10,a+=ch-'0';
}
void mobius(int n){
    int i,j;
    for(miu[1]=1,i=2;i<=n;i++){
        if(!v[i])p[tot++]=i,miu[i]=-1;
        for(j=0;j<tot&&i*p[j]<=n;j++){
            v[i*p[j]]=1;
            if(i%p[j])miu[i*p[j]]=-miu[i];else break;
        }
    }for(i=1;i<n;i++)sum[i]=sum[i-1]+miu[i];
}
LL pow(LL a,LL b,LL p){if(b==0)return 1;LL t=1;for(a%=p;b;b>>=1LL,a=a*a%p)if(b&1LL)t=t*a%p;return t;}
int main(){
    read(T);
    mobius(100000);
    while(T--){
        read(n);
        ans=0; int mn=~0U>>1,mx=0;
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)read(a[i]),mn=min(a[i],mn),mx=max(a[i],mx),cnt[a[i]]++;
        for(int i=1;i<=200000;i++)cnt[i]+=cnt[i-1]; 
        for(int i=2;i<=mn;i++){
            if(!miu[i])continue;
            LL tmp=1; 
            for(int j=1;i*j<=100000;j++)tmp=tmp*pow(j,cnt[i*j+i-1]-cnt[i*j-1],mod)%mod;
            // j<=100000/i -> i*j<=100000 : TLE -> AC
            ans=(ans-tmp*miu[i]+mod)%mod;
        }printf("Case #%d: %lld\n",cas++,ans);
    }return 0;
}
posted @ 2017-07-30 02:39  forever97  阅读(347)  评论(0编辑  收藏  举报