[HDU6035] TrickGCD

Problem Link

Description

    给定一个长度为 n 的序列 A1,A2,,An ,求满足 1BiAi gcd(B1,B2,,Bn)2 的序列 B 的个数,答案 mod 1e9+7

Solution

    大家用的都是莫比乌斯反演,但是我不会,于是从一个大牛那边学到了更容易理解的方法。我们定义 f(i) gcd=i B 的个数,但是直接算出这个数是不现实的,我们不妨设 F(i) gcd=i B 的个数,那么显然

F(i)=j=1n Aji

     F(i)=f(i)+f(2i)+f(3i)++f(ki) ,那么我们求 f(i) 就容易了, f(i)=F(i)f(2i)f(3i)f(ki) ,我们只要倒着 for 一遍,求出 F(i) ,再减去 f(i) 即可(因为在这之前, f(i) 已经被处理过了)。既然知道了如何求 f(i) ,接下来的任务就是求 F(i) 了。根据上面的公式,对于 i ,我们如果直接扫一遍数组求 F(i) 的话复杂度时 O(n) 的,整个复杂度就差不多是 O(n2) 的,这显然很慢。对于这里,我们可以换个方法,我们知道 Aji 值最多只有 n ,并且随着 i 的增大而减小,这样复杂度就很小了,对于每个 i ,我们枚举 Aji 的值,这样总的复杂度只有 O(nloglogn) 了,再减去 f(i) ,这样总的复杂度又是 O(nloglogn)

Code

#include
#include
#include
#include
#define M 100005
#define P 1000000007
using namespace std;
template <class T>
inline void Rd(T &res){
    char c;res=0;int k=1;
    while(c=getchar(),c<48&&c!='-');
    if(c=='-'){k=-1;c='0';}
    do{
        res=(res<<3)+(res<<1)+(c^48);
    }while(c=getchar(),c>=48);
    res*=k;
}
template <class T>
inline void Pt(T res){
    if(res<0){
        putchar('-');
        res=-res;
    }
    if(res>=10)Pt(res/10);
    putchar(res%10+48);
}
int n,ans;
int f[M];
int sum[M];
int T;
void Mod_mns(int &a,int b){
    if((a-=b)<0)a+=P;
}
int pow(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=1LL*res*a%P;
        a=1LL*a*a%P;
        b>>=1;
    }
    return res;
}
void solve(){
    int x,mi=2e9,mx=0;
    memset(sum,0,sizeof(sum));
    memset(f,0,sizeof(f));
    ans=0;
    Rd(n);
    for(int i=1;i<=n;i++){
        Rd(x);sum[x]++;
        mi=min(mi,x);
        mx=max(mx,x);
    }
    for(int i=1;i<=mx;i++)
    sum[i]+=sum[i-1];
    for(int i=mi;i>=2;i--){
        f[i]=1;
        for(int j=i;j<=mx;j+=i){
            int t=sum[min(mx,j+i-1)]-sum[j-1];
            f[i]=1LL*f[i]*pow(j/i,t)%P;
        }
        for(int j=i+i;j<=mx;j+=i)
        Mod_mns(f[i],f[j]);
        ans=(ans+f[i])%P;
    }
}
int main(){
    Rd(T);
    for(int i=1;i<=T;i++){
        solve();
        printf("Case #%d: %d\n",i,ans);
    }
    return 0;
}

你可能感兴趣的:(多校)