莫比莫斯小结

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

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

                                                  

           

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

 

     

 

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

 

    (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 这位大佬里面提到了对前缀和的处理,很详细。

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

 

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