GCD HDU - 1695 (容斥定理or莫比乌斯反演)

题目

题意:给出a b c d k,找到a <= x <= b ,c <= y <=d 使GCD(x,y)=k.

题中说a=c=1;

这个题可以用容斥定理做(如果还没有学习莫比乌斯反演的话,不过时间复杂度要高很多)。

容斥:

k=1时,我们可以看作求互素的数,当k!=1,我们使a b c d k 全部缩小k倍;这样就又转化为求互素的数啦。

此时a=c=1,b=b/k,d=d/k,k=1;

GCD(x,y)和GCD(y,x)是一种情况

i 的范围【a , b】,求 i 在 【c,d 】 范围内互素的数时,会有重复,应求 i 在【 i , d】 范围内互素的数。

(这里要确保d>b)。

时间(780ms)。

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
#include<vector>
#define ll long long
ll op[110];
ll s;
void prime(ll n) //求素因数
{
    s=0;
    for(ll j=2; j*j<=n; j++)
    {
        if(n%j==0)
        {
            while(n%j==0)
                n/=j;
            op[s++]=j;
        }
    }
    if(n!=1)
        op[s++]=n;
}
ll nop(ll m)  //容斥
{
    ll ha[1000];  //素因数及素因数的倍数
    ll top=0;
    ha[top++]=-1;
    for(ll i=0; i<s; i++)
    {
        ll t=top;
        for(ll j=0; j<t; j++)
            ha[top++]=ha[j]*op[i]*(-1);
    }
    ll sum=0;
    for(ll i=1; i<top; i++)
        sum+=m/ha[i];
    return sum;
}
int main()
{
    ll t,o=0;
    scanf("%lld",&t);
    while(t--)
    {
        o++;
        ll a,b,c,d,k;
        scanf("%lld %lld %lld %lld %lld",&a,&b,&c,&d,&k);
        if(k==0)
        {
            printf("Case %lld: 0\n",o);
            continue;
        }
        b=b/k;
        d=d/k;
        if(b>d)  //保证d>b
        {
            int x=b;
            b=d;
            d=x;
        }
        ll sum=0;
        for(ll i=1; i<=b; i++)
        {
            prime(i);
            sum+=d-nop(d);   //d-(i-1)即为范围 [i,d];
            sum-=(i-1-nop(i-1));
        }
        printf("Case %lld: %lld\n",o,sum);
    }
}

莫比乌斯反演:

本来这个题是用容斥做的,百度发现大家都用的莫比乌斯,所以现学了。。。

首先 n分解 为素因数之后是这样的形式:n=p1^{\alpha 1}*p2^{\alpha 2}*...*pk^{\alpha k}

 莫比乌斯函数 \mu 的值有这样的规律:

1.     n=1时  \mu (1)=1 

2.1    \alpha 1=\alpha 2=...=\alpha k=1     \mu (n)=\mu (p1^{\alpha 1})\mu (p2^{\alpha 2})...\mu (pk^{\alpha k})=(-1)^{k}

2.2   其他情况                                 \mu (n)=0; 

 

f(d) : GCD(x,y)=d 的(x,y) 的对数。  ( 1<=x<=n,1<=y<=m);

F(d) : d|GCD(x,y)  的(x,y) 的对数。  (d|a  指a是d的倍数)

 F(d) =\frac{n}{d}*\frac{m}{d}                           F(x) =\sum_{d|x} f(d);  

反演公式:

 f(x) =\sum _{x|d} \mu (\frac{d}{x})F(d) =\sum _{x|d}\mu (\frac{d}{x})*(\frac{n}{d})*(\frac{m}{d})        (1<= d/x <=min(n,m));

(写公式好麻烦AAAA

求莫比乌斯函数可用欧拉筛:

int mu[10000];  //记录莫比乌斯函数
void mo(int n)
{

    memset(book,0,sizeof(book));
    book[1]=1;
    mu[1]=1;    //n=1时
    s=0; 
    for(int i=2; i<n; i++)
    {
        if(!book[i])
        {
            prime[s++]=i;
            mu[i]=-1;  //i为质数时
        }
        for(int j=0; j<s&&i*prime[j]<=n; j++)
        {
            book[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                mu[i*prime[j]]=0;  //次方数不全为1
                break;
            }
            mu[i*prime[j]]=-mu[i];
        }
    }
}

 

之后根据反演公式求出 f(1) 的值sum。

去重时,将公式中的n,m换为min(n,m) 求出两个公共部分的 f(1) 的值 ans , ans/2即为多余部分。

sum-ans/2 即为所求值。

时间(30ms)

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
#include<vector>
#define ll long long
ll prime[110000];  
int mu[110000];
bool book[110000];
ll s;
void mo(int n)
{
    book[1]=1;
    mu[1]=1;
    s=0;
    for(int i=2; i<=n; i++)
    {
        if(!book[i])
        {
            prime[s++]=i;
            mu[i]=-1;
        }
        for(int j=0; j<s&&i*prime[j]<=n; j++)
        {
            book[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                mu[i*prime[j]]=0;
                break;
            }
            mu[i*prime[j]]=-mu[i];
        }
    }
}
int main()
{
    ll t,o=0;
    mo(100000);
    scanf("%lld",&t);
    while(t--)
    {
        o++;
        ll a,b,c,d,k;
        scanf("%lld %lld %lld %lld %lld",&a,&b,&c,&d,&k);
        if(k==0)
        {
            printf("Case %lld: 0\n",o);
            continue;
        }
        b=b/k;
        d=d/k;
        ll minn=min(b,d);
        ll sum=0,ans=0;
        for(ll i=1; i<=minn; i++)
        {
           sum+=mu[i]*(b/i)*(d/i);
           ans+=mu[i]*(minn/i)*(minn/i);
        }
        sum-=ans/2;
        printf("Case %lld: %lld\n",o,sum);
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值