hdu 6053 trick gcd 容斥

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

题意:给定一个数组,我们定义一个新的数组b满足bi<ai 求满足gcd(b1,b2....bn)>=2的数组b的个数

题解:利用容斥定理。我们先定义一个集合f(x)表示gcd(b1,b2...bn)为x倍数的个数(x为质数),我们在定义一个数mi为数组中的最小值,那么集合{f(2)Uf(3)....f(n)}就是我们想要的答案。f(x)=(a1/x)*(a2/x)*.....(ai/x),直接累加肯定是有重复的,我们得用容斥定理筛一下,如果x是奇数个不同素数因子的乘积最后的结果要加上f(x);如果x为偶数个不同素数因子的乘积,最后的结果要减去f(x),其他情况贡献为0。是不是和莫比乌斯函数的情况正好相反?这里筛值的时候,用0(n)求到的莫比乌斯函数筛时间复杂度还是可以的。光这样还是不够,因为数组的长度为1e5,我们求f(x)的时候也得优化,怎么优化呢。我们把用一个权值数组把a[i]的值离散上去,为啥要用权值的形式存放a数组的值呢,我们把权值数组分成x段,每段的贡献由1开始递增到x(这段比较抽象,具体看下代码)

ac代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
ll mu[100001];
int prime[100001],vis[100001];
ll a[100001],sum[200001];
// 比较大的数组还是定义在外面比较好
ll qpow(ll a,ll b)
{
    ll f=1;
    while(b)
    {
        if(b%2==1) f=(f*a)%mod;
        a=(a*a)%mod;
        b/=2;
    }
    return f;
}

void init()
{
    mu[1]=1;
    memset(mu,0,sizeof(mu));
    memset(prime,0,sizeof(prime));
    memset(vis,0,sizeof(vis));
    int ret=0;
    for(int i=2;i<100007;i++)
    {
        if(!vis[i])
        {
           prime[ret++]=i;
           mu[i]=-1LL;
        }
        for(int j=0; j<ret && i*prime[j] < 100007;j++)
        {
            int temp=i*prime[j];
            vis[temp]=1;
            if(i%prime[j]) mu[temp]=-mu[i];
            else
            {
                mu[temp]=0;
                break;
            }
        }
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    init();

    int Case=0;
    while(t--)
    {
        ll n;
        cin>>n;
        ll mi=100005;
        memset(sum,0,sizeof(sum));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            mi=min(a[i],mi);
            sum[a[i]]++;
        }
        for(int i=1;i<=200000;i++) sum[i]+=sum[i-1];
        ll zz=0;
        for(int i=2;i<=mi;i++)
        {
            ll ans=1;
            if(mu[i]==0) continue;
            for(int j=1;j*i<=100000;j++)// 平铺分段的思想  枚举贡献的思想吧,对于f(x)来说,x/n相同的值比较多,这样就可以把问题的规模变小 这个思维比较常见
            {
                ans=(ans*qpow(j,sum[i*(j+1)-1]-sum[j*i-1])%mod)%mod;
            }
            zz=(zz-mu[i]*ans%mod+mod)%mod;// ! 取模的时候 如果有减法 要注意
        }
        printf("Case #%d: ",++Case);
        cout<<zz<<endl;
    }
    return 0;
}

 

posted @ 2017-08-03 10:35  猪突猛进!!!  阅读(210)  评论(0编辑  收藏  举报