莫比乌斯反演入门题目(详细)

很详细的题解,适合刚学莫比乌斯反演找题做的初学者.
公式恐惧者要克服恐惧
食用须知请先学会以下内容,
线性筛(大米饼)
莫比乌斯反演(pengym)
\[写在前面的话\]
刚学会莫比乌斯反演,就去欢乐的切题啦,发现实在是肝不动,利用两天时间才肝了一下几道题.
发现莫比乌斯反演就是多刷题,发现莫比乌斯反演就是容斥.莫比乌斯反演就是数论反演
-----------by Gzy

luoguP2568 GCD

链接:luoguP2568 && bzoj2818: GCD

题目描述

给定整数N,求1<=x,y<=N且Gcd(x,y)为素数的数对(x,y)有多少对.

输入输出格式

输入格式:

一个整数n

输出格式:

答案

输入输出样例

输入样例#1:
4
输出样例#1: 复制
4
题解
考虑每个素数对答案的贡献.
\[gcd(x,y) = p\]
\[gcd(x/p,y/p) = 1\]
就转化成了两个互质的数对对答案贡献1.线性筛phi,求一个前缀和即可.
因为\[(x,y) = (y,x) (x ≠ y)\]所以ans要乘2,由于还有gcd(1,1)这个特殊情况,再-1

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>

const int X = 10000001;
int n,num;
long long prime[X],phi[X];
bool is_prime[X];

void init() {
    phi[1] = 1; 
    for(int i = 2;i <= n;++ i){
        if(!is_prime[i]){
            prime[num ++] = i;
            phi[i] = i - 1;
        }   
        for(int j = 0;j < num && i * prime[j] <= n;++ j){
            is_prime[i * prime[j]] = true;
            if(i % prime[j] == 0){
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
            else{
                phi[i * prime[j]] = phi[i] * (prime[j] - 1);
            }
        }
    }
}

int main(){
    scanf("%d",&n);
    init();    
    for(int i = 2;i <= n;++ i)phi[i] += phi[i - 1];
    long long ans = 0;
    for(int i = 0;i < num;++ i)ans += phi[n / prime[i]] * 2 - 1;
    printf("%lld",ans);
    return 0;
}

hdu1695 GCD

题目链接:HDU 1695

Problem Description

Given 5 integers: a, b, c, d, k, you're to find x in a...b, y in c...d that GCD(x, y) = k. GCD(x, y)means the greatest common divisor of x and y. Since the number of choices may be very large, you're only required to output the total number of different number pairs.Please notice that, (x=5, y=7) and (x=7, y=5) are considered to be the same.Yoiu can assume that a = c = 1 in all test cases.

Input

The input consists of several test cases. The first line of the input is the number of the cases. There are no more than 3,000 cases.
Each case contains five integers: a, b, c, d, k, 0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000, as described above.

Output

For each test case, print the number of choices. Use the format in the example.

Sample Input

2
1 3 1 5 1
1 11014 1 14409 9

扫描二维码关注公众号,回复: 2588254 查看本文章
Sample Output

Case 1: 9
Case 2: 736427

HintFor the first sample input, all the 9 pairs of numbers are (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 5), (3, 4), (3, 5).

注意a = 1,c = 1,此题细节很多,我都标了括号.
题意:让你求出\[\sum_{i=1}^{b}\sum_{j=1}^{d}[gcd(x,y)=d]\]
跟T1的思路差不多.\[gcd(x,y) = k;gcd(x/k,y/k) = 1\]
然后我们怎么求呢.现在开始莫比乌斯反演.
设函数\[f(d) 是 gcd(x,y) = d\]的对数的个数.
F(d) = \[\sum_{d|n}f(n)\]的对数.
\(d|gcd(x,y)\)
\(d|x,d|y\)
\(F(x) = (a/x) * (b / x)\)(x中有多少x的倍数,y中有多少d的倍数)
反演得到:
\[f(n)=\sum_{n|d}\mu{[d]}F(d)\]
∵我们求得是gcd(x,y) = 1,根据f函数的定义
∴我们求f(1)
f(1) = \[\sum_{i=1}^{min(a,b)} \mu [d]F(d)\](枚举到min(a,b),因为F(d),d最大为min(a,b))
然后欢乐的枚举就好了.
根据题面,我们发现这样会有重复的.
\(gcd(x,y) = gcd(y,x) = 1\)但这样并不被允许,所以我们利用容斥原理,将它删去.
因为重复的都是min(a,b)里面的,所以只要将min(a,b) 到max(a,b)中的数显然不会有重复.
\[\sum_{i=1}^{min(a,b)}\sum_{j=1}^{min(a,b)} [gcd(i,j)==k]\]的答案减半,然后用刚才求得答案减去就好了

#include <iostream>
#include <cstdio>
#define ll long long
const ll maxN = 100010;

ll mu[maxN];
bool vis[maxN];
ll prime[maxN];
ll num;

void init() {
    vis[1] = 1;
    mu[1] = 1;
    for(ll i = 2;i <= 100000;++ i) {
        if( !vis[i] ) {
            prime[++ num] = i;
            mu[i] = -1;
        }
        for(ll j = 1;j <= num && i * prime[j] <= 100000;++ j) {
            vis[i * prime[j]] = true;
            if(i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
}

inline void swap(ll &a,ll &b) {
    a ^= b ^= a ^= b;
    return;
}

int main() {
    ll T;
    ll tot = 0;
    init();
    scanf("%lld",&T);
    while(T --) {
        ll qaqqqa,qaqqaq,a,b,k;
        scanf("%lld%lld%lld%lld%lld",&qaqqqa,&a,&qaqqaq,&b,&k);
        if( !k ) {printf("Case %lld: 0\n",++tot);continue;} 
        printf("Case %lld: ",++ tot);
        a = (a / k);b = (b / k);
        if(a > b) 
            swap(a,b);
        ll f = 0 ;
        ll f2 = 0;
        for(ll i = 1;i <= a;++ i) 
            f += mu[i] * (a / i) * (b / i);
        for(ll i = 1;i <= a;++ i)
            f2 += mu[i] * (a / i) * (a / i);
        printf("%lld\n",f - f2 / 2);
    }
    return 0;
}

luogu3455[POI2007]ZAP-Queries

题目链接:ZAP-Queries

题目描述

Byteasar the Cryptographer works on breaking the code of BSA (Byteotian Security Agency). He has alreadyfound out that whilst deciphering a message he will have to answer multiple queries of the form"for givenintegers aa , bb and dd , find the number of integer pairs (x,y)(x,y) satisfying the following conditions:

$1\le x\le a1≤x≤a , 1\le y\le b1≤y≤b , gcd(x,y)=dgcd(x,y)=d $, where \(gcd(x,y)gcd(x,y)\) is the greatest common divisor of xx and yy ".

Byteasar would like to automate his work, so he has asked for your help.

TaskWrite a programme which:

reads from the standard input a list of queries, which the Byteasar has to give answer to, calculates answers to the queries, writes the outcome to the standard output.

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

输入输出格式
输入格式:

The first line of the standard input contains one integer nn ( 1\le n\le 50 0001≤n≤50 000 ),denoting the number of queries.

The following nn lines contain three integers each: aa , bb and dd ( 1\le d\le a,b\le 50 0001≤d≤a,b≤50 000 ), separated by single spaces.

Each triplet denotes a single query.

输出格式:

Your programme should write nn lines to the standard output. The ii 'th line should contain a single integer: theanswer to the ii 'th query from the standard input.
输入样例#1
2
4 5 2
6 4 3
输出样例#1
3
2
有没有发现跟上一个题只修改了一部分,差异:不需要计算重复部分.
那就非常简单了,貌似没有细节.但是,我们会发现,即使O(n*m)时间复杂度也不过了.
所以这个题就会用到数论分块(注意使用数论分块的那部分).如果不会,可以参考数论分块

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#define ll long long
const int maxN = 50000 + 7;
ll mu[maxN];
ll prime[maxN];
bool vis[maxN]; 
ll sum[maxN];

void init() {
    int num = 0;
    mu[1] = 1;
    vis[1] = true;
    for(int i = 2;i <= 50000;++ i) {
        if( !vis[i] ) {
            prime[++ num] = i;
            mu[i] = -1;
        }
        for(int j = 1;j <= num && prime[j] * i <= 50000;++ j) {
            vis[prime[j] * i] = true;
            if(i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
    for(int i = 1;i <= 50000;++ i) 
        sum[i] = sum[i - 1] + mu[i];
    return ;
}

void swap(int &a,int &b) {
    a ^= b ^= a ^= b;
    return;
}

int min(int a,int b) {
    return a > b ? b : a ;
}

int main() {
    int T;
    scanf("%d",&T);
    init();
    while(T --) {
        int a,b,k;
        scanf("%d%d%d",&a,&b,&k);
        a /= k;b /= k;
        if(a > b) swap(a,b);
        long long ans = 0;
        for(int l = 1,r;l <= a;l = r + 1) {
            r = min(a / (a / l),b / (b / l));
            ans += 1LL * ( sum[r] - sum[l - 1] ) * (a / l) * (b / l);
        }
        printf("%lld\n",ans);
    }
}

luogu2522 [HAOI2011]Problem b

题目链接P2522 [HAOI2011]Problemb

题目描述

对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k,gcd(x,y)函数为x和y的最大公约数.

输入输出格式
输入格式:

第一行一个整数n,接下来n行每行五个整数,分别表示a、b、c、d、k

输出格式:

共n行,每行一个整数表示满足要求的数对(x,y)的个数

输入输出样例

输入样例#1:
2
2 5 1 5 1
1 5 1 5 2
输出样例#1:
14
3
说明
100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000
同上一题差不多,但是多了区间,利用容斥原理加加减减的就好了.容斥下来是这样的.
\[Ans((1,b),(1,d))−Ans((1,b),(1,c−1))−Ans((1,a−1),(1,d))+Ans((1,a−1),(1,c−1))\]
不多说,上代码,没什么细节.

#include <iostream>
#include <cstdio>
#define ll long long
const int maxN = 50000 + 7;

bool vis[maxN];
ll prime[maxN];
ll mu[maxN];
ll sum[maxN];
ll a,b,c,d,k;

inline ll read() {
    ll x = 0,f = 1;char c = getchar();
    while(c < '0' || c > '9') {if(c == '-')f = -1;c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
    return x * f;
}

void init() {
    ll num = 0;
    mu[1] = 1;
    vis[1] = 1;
    for(ll i = 2;i <= 50000;++ i) {
        if( !vis[i] ) {
            prime[++ num] = i;
            mu[i] = -1;
        }
        for(ll j = 1;j <= num && i * prime[j] <= 50000;++ j) {
            vis[i * prime[j]] = true;
            if( i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
    for(ll i = 1;i <= 50000;++ i) 
        sum[i] = sum[i - 1] + mu[i];
    return ;
}

void swap(ll &a,ll &b) {
    a ^= b ^= a ^= b;
    return;
}

ll min(ll a,ll b) {
    return a > b ? b : a;
}

ll slove(ll l,ll r) {
    l /= k;r /= k;
    if(l > r) swap(l,r);
    ll ans = 0;
    for(ll L = 1,R;L <= l;L = R + 1) {
        R = min(l / (l / L),r / (r / L));
        ans += (sum[R] - sum[L - 1]) * (l / L) * (r / L);
    }
    return ans;
}

int main() {
    ll T;
    T = read();
    init();
    while(T --) {
        a = read();b = read();c = read();d = read();k = read();
        if( !k ) {
            puts("0");
            continue;
        } 
        printf("%lld\n",slove(b,d) - slove(a - 1,d) - slove(c - 1,b) + slove(a - 1,c - 1)); 
    }
}

刷着刷着就刷不动了,因为我遇到了一个很奇怪的题目.

P4318 完全平方数

题目描述

小 X 自幼就很喜欢数。但奇怪的是,他十分讨厌完全平方数。他觉得这些数看起来很令人难受。由此,他也讨厌所有是完全平方数的正整数倍的数。然而这丝毫不影响他对其他数的热爱。这天是小X的生日,小 W 想送一个数给他作为生日礼物。当然他不能送一个小X讨厌的数。他列出了所有小X不讨厌的数,然后选取了第 K个数送给了小X。小X很开心地收下了。然而现在小 W 却记不起送给小X的是哪个数了。你能帮他一下吗?

输入输出格式
输入格式:

包含多组测试数据。文件第一行有一个整数 \(T\) ,表示测试数据的组数.第\(2\) 至第 \(T+1\) 行每行有一个整数 \(K_i\) ,描述一组数据,含义如题目中所描述。

输出格式:

含T 行,分别对每组数据作出回答。第 \(i\) 行输出相应的第 \(K_i\) 个不是完全平方数的正整数倍的数。

输入输出样例

输入样例#1:
4
1
13
100
1234567
输出样例#1:
1
19
163
2030745
说明
对于 50%的数据有 \(1 ≤ K_i ≤ 10^5\) , 对于 100%的数据有 \(1 ≤ K_i ≤ 10^9, T ≤ 50\)
题意意思是求第k个无平方因子数是多少。无平方因子数,即对其进行质因数分解之后所有质因数的次数都为1的数.
一看这道题,不就是一个\(\mu\)函数的妙用么,统计一下不为0的\(\mu\)值不就好了,秒了.
然后码了以下代码.

#include <iostream>
#include <cstdio>
const int maxN =  10000000 + 7;

int prime[maxN / 10];
bool vis[maxN];
int mu[maxN];

void init() {
    int num = 0;
    mu[1] = 1;
    vis[1] = 1;
    for(int i = 2;i <= 10000000;++ i) {
        if( !vis[i] ) {
            prime[++ num] = i;
            mu[i] = -1; 
        }
        for(int j = 1;j <= num && i * prime[j] <= 10000000;++ j) {
            vis[i * prime[j]] = true;
            if(i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
}

int slove(int k) {
    int num = 0;
    for(int i = 1;i <= 10000000;++ i) {
        if(mu[i] == 1 || mu[i] == -1) num++;
        if(num == k) return i;
    }
}

int main() {
    int T;
    scanf("%d",&T);
    init();
    while(T --) {
        int k;
        scanf("%d",&k);
        printf("%d\n",slove(k));
    }
    return 0;
}

然后很快的我TLE了三个点,一看数据范围,O(n)也过不去,只有类似于O(\(\sqrt n\))的时间复杂度才可行.
\(k=x-\sum_{i=1}^{x}{(1-|\mu(i)|)}\)(x不确定)
因为k不一定在哪一个x,我们会发现可以x越大区间[1,k]的无平方因子数越多,那么就可以欢快的二分了.
但是如何求区间[1,x]的无平方因子数呢.还是考虑容斥.
设当前二分到了x,那么不符合条件的数是.
\[\sum_{i\in{prime}}^{\sqrt{x}}{\frac{x}{i^2}} \]
显然这个式子会重复计算一个数,我们再减去重复的,同理剩下的也是这样.
容易发现前面的系数是\(\mu [i]\)(我不会证....QAQ)
式子就变为\[k=x-\sum_{i=1}^{\sqrt{x}}{\mu(i)*\frac{x}{i^2}}\]
二分的上界可以自己测出来.
还有一道类似的题目:[vijos1889]天真的因数分解跟这道题差不多,代码就懒得写了.
代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cmath>
#include <cstdio>
#define ll long long
const ll maxN = 50000 + 7;
using namespace std;

bool vis[maxN];
ll prime[maxN];
ll mu[maxN];
ll sum[maxN];

void init() {
    ll num = 0;
    mu[1] = 1;
    for(ll i = 2;i <= 50000;++ i) {
        if( !vis[i] ) {
            prime[++ num] = i;
            mu[i] = -1;
        }
        for(ll j = 1;j <= num && i * prime[j] <= 50000;++ j) {
            vis[i * prime[j]] = true;
            if(i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
        
    }
    
    return ;
}

ll work(ll k) {
    ll ans = 0;
    ll f = sqrt(k);
    for(ll l = 1;l <= f;++ l) {
        ans += mu[l] * (k / l / l);
    }
    return ans;
}

bool calc(ll x,ll k) {
    return x >= k;
}

int main() {
    int T;
    init();
    scanf("%d",&T);
    while(T --) {
        ll k;
        scanf("%lld",&k);
        ll l = 1,r = 2000000000,ans;
        while(l <= r) {
            ll mid = (l + r) >> 1;
            ll x = work(mid);
            if(calc(x,k)) {
                ans = mid;
                r = mid - 1;
            }
            else l = mid + 1;
        }
        printf("%lld\n",ans);
    }
    return 0;
    
}

luoguP1403[AHOI2005]约数研究

题链:luoguP1403[AHOI2005]约数研究

题目描述

科学家们在Samuel星球上的探险得到了丰富的能源储备,这使得空间站中大型计算机“Samuel II”的长时间运算成为了可能。由于在去年一年的辛苦工作取得了不错的成绩,小联被允许用“Samuel II”进行数学研究.
小联最近在研究和约数有关的问题,他统计每个正数N的约数的个数,并以f(N)来表示。例如12的约数有1、2、3、4、6、12。因此f(12)=6。下表给出了一些f(N)的取值:

f(n)表示n的约数个数,现在给出n,要求求出f(1)到f(n)的总和。

输入输出格式
输入格式:

输入一行,一个整数n

输出格式:

输出一个整数,表示总和

输入输出样例

输入样例#1: 复制
3
输出样例#1: 复制
5
说明
【数据范围】
20%N<=5000
100%N<=1000000
直接无语了,竟然被评为了普及,QAQ,发现自己数学简直是渣渣,这么明显的结论推不出.
其实题目让求.\(\sum_{i=1}^{n}f[i]\)
就是因子个数,换个角度,看看每个数被用了多少次,就是\(\sum_{i=1}^{n}\lfloor\frac ni\rfloor\)
可以不用数论分块求.O(n)的可过.

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#define ll long long

ll slove(ll n) {
    ll ans = 0;
    for(ll l = 1,r;l <= n;l = r + 1) {
        r = n /(n / l);
        ans += (r - l + 1) * ( n / l);
    }
    return ans;
}

int main() {
    ll l,r;
    scanf("%lld",&r);
    printf("%lld",(slove(r)));
    return 0;
}

luoguP3935 Calculating

切了这道题后, 然后再切这么一道水题.
题目链接:(luoguP3935 Calculating)[https://www.luogu.org/record/show?rid=9372519]
先容斥然后
用到一个定理:因数个数定理
\(n=p_1^{k_1}*p_2^{k_2}*...*p_m^{k_m}\)其中 \(p_1,p_2,...,p_m\)均为互不相等的质数
则n的因数个数为$\prod_{i=1}^m{(a_i+1)} $
知道了就和上题差不多.多学点定理吧,骚年.

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#define ll long long
#define mod 998244353

ll slove(ll n) {
    ll ans = 0;
    for(ll l = 1,r;l <= n;l = r + 1) {
        r = n /(n / l);
        ans += ((r - l + 1) % mod * ( n / l) % mod) % mod;
        ans %= mod;
    }
    return ans % mod;
}

int main() {
    ll l,r;
    scanf("%lld%lld",&l,&r);
    printf("%lld",((slove(r) % mod) - (slove(l - 1) % mod) + mod) % mod)% mod;
    return 0;
}

完结撒花.

猜你喜欢

转载自www.cnblogs.com/tpgzy/p/9433607.html
今日推荐