zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    Altium Designer中各层的含义
    国外现在有哪些众筹网站呢?
    C# 代码 手工 配置 Log4Net 2种方法
    Windows 7 里进程管理器里面的各列是什么含义?主要是和内存有关的内存-专用工作集,内存-工作集,内存-提交大小???
    C# .Net 下 x86使用大内存的处理
    CV学习日志:CV开发之关联Gazebo/Webots/ROS2
    CV学习日志:CV开发之Windows10环境搭建
    CV学习日志:CV开发之Ubuntu2004和WLS2环境搭建
    ROS2学习日志:ROS2Cartgrapher使用与调试
    ROS2学习日志:QoS要点总结
  • 原文地址:https://www.cnblogs.com/z1141000271/p/7278395.html
Copyright © 2011-2022 走看看