• 莫比莫斯小结


    前几天多校的时候碰到一道莫比乌斯的题目,一脸懵逼,然后恶补了几道。

    先看下莫比乌斯的基本概念:

                                                      

               

    定理:是定义在非负整数集合上的两个函数,并且满足条件,那么我们得到结论

         

    在上面的公式中有一个函数,它的定义如下:

        (1)若,那么

        (2)若均为互异素数,那么

        (3)其它情况下

     来自acdreamer大佬的博客。

    先说下第一道入门题目(虽然入门,处理的东西也不少。。):

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

    题目意思:对于1<=x<=d,1<=y<=d 求gcd(x,y)=k的个数

    题解:首先第一步,对于对于1<=x<=d,1<=y<=d 如果 gcd(x,y)=k,那么对于1<=x/k<=d/k,1<=y/k<=d/k gcd(x,y)=1。问题就转化为对于1<=x<=d/k,1<=y<=d/k 求gcd(x,y)=1的个数。

    然后我们定义两个函数。F(n)与f(n);

    F(d)为 有多少对(x,y)满足 gcd(x,y)== d 的倍数 。

    f(d)为有多少对(x,y)满足 gcd(x,y)== d  。

    我们要求的是f(d)的值,但我们发现F(d)函数比较容易求解F(d)=(n/d)*(m/d);(这个怎么理解呢,我们把范围1~n里面能够整除x的数的个数表示为n/x;对于两个数,如果两个数的因子都有d,那么这两个数的倍数的gcd一定是d的倍数)。

    是不是可以套进去?

    而且我们简化之后,要求的n为1 那么u也就很好求了。

    贴一个线性求mu的代码:

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

    有了这些之后,还有一个问题。题目要求去重,这个怎么理解呢?

    此时,走到这一步,我们已经求得了(x,y)满足 gcd(x,y)=1 的对数 ,但题目中说明了,(1,2)和(2,1)算一种情况,那么我们就要减去多余了的情况,怎那么找出那些多算进去的情况呢? 下面的图画的很清楚:

       G(b,b)就是多算进去的这些情况,

       那么 G(b,d)- G(b,b)/ 2 就是最终我们要求的结果了,至于这一点,有不懂的请在纸上画一画,这不是我要讲的重点了。(转自http://blog.csdn.net/lixuepeng_001/article/details/50577932)

    ac代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;  
      
    const int maxn=1e5+7;  
    bool vis[maxn];  
    int prime[maxn],mu[maxn];  
    int cnt;  
      
    void Init(){  
        int N=maxn;  
        memset(prime,0,sizeof(prime));  
        memset(mu,0,sizeof(mu));  
        memset(vis,0,sizeof(vis));  
        mu[1] = 1;  
        cnt = 0;  
        for(int i=2; i<N; i++){  
            if(!vis[i]){  
                prime[cnt++] = i;  
                mu[i] = -1;  
            }  
            for(int j=0; j<cnt&&i*prime[j]<N; j++){  
                vis[i*prime[j]] = 1;  
                if(i%prime[j]) mu[i*prime[j]] = -mu[i];  
                else{  
                    mu[i*prime[j]] = 0;  
                    break;  
                }  
            }  
        }  
    }  
      
    void getMu(){  
        int N=maxn;  
        for(int i=1;i<N;++i){  
            int target=i==1?1:0;  
            int delta=target-mu[i];  
            mu[i]=delta;  
            for(int j=2*i;j<N;j+=i)  
                mu[j]+=delta;  
        }  
    }  
      
    int main()  
    {  
        ios::sync_with_stdio(false);  
        int a,b,c,d,k;  
        int T,Case=0;  
        Init();  
        cin>>T;  
        while(T--){  
            cin>>a>>b>>c>>d>>k;  
            cout<<"Case "<<++Case<<": ";  
            if(k==0){  
                cout<<"0"<<endl;  
                continue;  
            }  
            b/=k,d/=k;  
            long long ans1=0,ans2=0;  
            for(int i=1;i<=min(b,d);i++){  
                ans1+=(long long)mu[i]*(b/i)*(d/i);  
            }  
            for(int i=1;i<=min(b,d);i++){  
                ans2+=(long long)mu[i]*(min(b,d)/i)*(min(b,d)/i);  
            }  
            cout<<ans1-ans2/2<<endl;  
        }  
        return 0;  
    }  

    对于这个问题还有一个知识点可以,就是分块求和。

    对于[n/x]这个表达式,当[n/x]=d是不是有很多x可以对应这个值?

    比如a'=100,那么d在[34,50]之间a'/d都是2。

     那么我们可以把连续的一段d一起来算(分块):

     设a'/d=x,那么最后一个a'/d=x的d=a'/x,所以这段连续的区间就是[d,a'/(a'/d)]

     结合b'/d,取个min就可以了。

    我们就不需要对每一个x都做一个遍历而是可以分块把复杂度降到根号n;

    bzoj1101:

    1101: [POI2007]Zap

    Time Limit: 10 Sec  Memory Limit: 162 MB
    Submit: 1400  Solved: 455
    [Submit][Status]

    Description

    FGD正在破解一段密码,他需要回答很多类似的问题:对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d。作为FGD的同学,FGD希望得到你的帮助。

    Input

    第一行包含一个正整数n,表示一共有n组询问。(1<=n<= 50000)接下来n行,每行表示一个询问,每行三个正整数,分别为a,b,d。(1<=d<=a,b<=50000)

    Output

    对于每组询问,输出到输出文件zap.out一个正整数,表示满足条件的整数对数。

    Sample Input

    2
    4 5 2
    6 4 3

    Sample Output

    3
    2

    HINT

    对于第一组询问,满足条件的整数对有(2,2),(2,4),(4,2)。对于第二组询问,满足条件的整数对有(6,3),(3,3)。

    参考代码:
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    typedef long long ll;
    int prime[100001];
    int mu[100001];
    int sum[100001];
    void init()
    {
        int vis[100001];
        memset(vis,0,sizeof(vis));
        memset(mu,0,sizeof(mu));
        memset(prime,0,sizeof(prime));
        mu[1]=1;
        int ret=0;
        for(int i=2;i<=100000;i++)
        {
            if(!vis[i])
            {
                prime[ret++]=i;
                mu[i]=-1;
            }
            for(int j=0;j<ret && i*prime[j] < 100000;j++)
            {
                int temp=i*prime[j];
                vis[temp]=1;
                if(i%prime[j]) mu[temp]=-mu[i];
                else
                {
                    mu[temp]=0;
                    break;
                }
            }
        }
        for(int i=1;i<=100001;i++)
        {
            sum[i]=sum[i-1]+mu[i];
        }
    }
    int main()
    {
        ios::sync_with_stdio(false);
        int t;
        cin>>t;
        init();
        int Case=0;
        while(t--)
        {
            int b,d,k;
            cin>>b>>d>>k;
            printf("Case %d: ",++Case);
            if(k==0)
            {
                cout<<"0"<<endl;
                continue;
            }
            ll temp1,temp2;
            temp1=temp2=0ll;
            d/=k;
            b/=k;
            int pos;
            for(int i=1;i<=min(b,d);i=pos+1)
            {
                pos=min(b/(b/i),d/(d/i));//求出分块区间
                temp1+=(sum[pos]-sum[i-1])*(b/i)*(d/i);// 对mu做一个前缀和的预处理
            }
            cout<<temp1<<endl;
    
        }
    
        return 0;
    }

    好以上都是对于求一定范围内gcd(x,y)=k的情况,那么要求gcd(x,y)为质数的情况呢?

    前面求质数的情况已经介绍过了,这里无非就是枚举一次范围内质数的个数,但是直接枚举是会超时的,我们还是要用到分块的思想。既然要用到分块的思想那么我们势必需要对前缀和做一个处理。http://www.cnblogs.com/iwtwiioi/p/4132095.html 这位大佬里面提到了对前缀和的处理,很详细。

    未完待续。。。看了将近三天才处理掉一下皮毛。。加油加油

  • 相关阅读:
    Double-Array Trie 原理解析
    LeetCode 之 Longest Valid Parentheses(栈)
    [Unity3D]Unity3D游戏开发之从Unity3D到Eclipse
    走进Struts2(一) — Struts2的执行流程及其工作原理
    网页页面NULL值对浏览器兼容性的影响
    基于cocos2d-x-3.2学习Box2D(一)
    记忆方法与高速阅读——什么是高速阅读
    ArcGIS 教程:Workflow Manager 高速浏览
    【cocos2d-x 3.7 飞机大战】 决战南海I (三) 敌机实现
    C语言事实上不简单:数组与指针
  • 原文地址:https://www.cnblogs.com/z1141000271/p/7267224.html
Copyright © 2020-2023  润新知