HDU 1695 GCD (莫比乌斯反演入门学习小结)

前言:

这些天在学习莫比乌斯的过程中看了许多博客和众多大牛的解释,然而可是博主太菜的原因,一直没能好好理解,今天偶然间看到了一份吉大附中的ppt,感觉瞬间开了窍,所以先把这个材料推荐给大家,然后我再这里总结一下自己的体会。

ppt地址: https://wenku.baidu.com/view/e6645609d15abe23492f4db0.html
(以下图片取自ppt,仅供学习与交流使用)

这里写图片描述

上图中的miu函数我们暂时不去在意,这个函数是莫比乌斯发现的一个刚好满足我们需要的性质的函数,暂时不考虑太多。(之后我们线性筛o(n)的筛出miu函数的值)
上边的公式是形式一,我们经常用他的另一种形式:

这里写图片描述

第二张图中左边的式子称为原式,右边的式子称为反演式。
然后我们先来解释一下式子中的符号吧:连加符号我就不多说了,重点说一下 n|d 这个符号吧,意思是d是n的倍数(换句话说,n是d的因子,n整除d)。连加符号代表枚举所有的d。

为什么要用莫比乌斯反演?

因为 f(n) 在很多时候很难求出来,而 F(n) 却可以暴力莽出来。举个栗子(大概浏览一下,不要仔细看):

2

这里写图片描述

然而我们这道题(hdu 1695)却无需那么麻烦,两个区间都是从1开始的,那么我们按照这个思路反演一下,就能得到所有有序对的组合了。
(然而的然而,本题要求(x,y)和(y,x)是一个。。
所以我们考虑我们已经得到的区间 (1,b)(1,d) (b<=d) 的有序对的个数,那么我们可知重复的一定是在 (1,b)(1,b) 中,所以减去其中的一半就是答案。也就是说做两次莫比乌斯反演。

下边是线性筛 素数(prime)+欧拉数(phi)+μ值(miu)基础的板子

#include<stdio.h>
#include <iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#define eps 1e-8
typedef long long int lli;
using namespace std;

const int maxn = 1e6+10;
bool isprime[maxn] = {1,1};
//int phi[maxn];
int prime[maxn],miu[maxn];
void moblus(){
    int cnt = 0;miu[1] = 1;
    for(lli i = 2;i < maxn;i++){
        if(!isprime[i]){
            prime[cnt++] = i,miu[i] = -1;//phi[i] = i-1;
        }
        for(lli j = 0;j < cnt && i*prime[j] < maxn;j++){
            lli x = prime[j];
            isprime[i*x] = 1;
            if(i%x){
                miu[i*x] = -miu[i];
                //phi[i*x] = phi[i] * phi[x];
            }
            else{
                miu[i*x] = 0;
                //phi[i*x] = phi[i] * x;
                break;
            }
        }
    }
}

int main(){
    moblus();
    lli n,p,cas,ncase = 0;
    scanf("%d",&cas);
    moblus();
    while(cas--){
        ncase++;
        lli a,b,c,d,k;
        scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
        if(k == 0){
            printf("Case %lld: 0\n",ncase);
            continue;
        }
        if(d<b) swap(d,b);
        lli ans = 0,ans2 = 0;
        for(int i = 1;;i++){
            if(i*k > b) break;
            ans += miu[i] * (b/(i*k)) * ((d/(i*k)));
        }
        for(int i = 1;;i++){
            if(i*k > b) break;
            ans2 += miu[i] * (b/(i*k)) * ((b/(i*k)));
        }
        printf("Case %lld: %lld\n",ncase,ans-ans2/2);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值