【数学专题】约数个数与欧拉函数

整理的算法模板合集: ACM模板


题目算法
AcWing 1291. 轻拍牛头求约数的个数
AcWing 1294. 樱花 ( n ! ) 2 (n!)^2 (n!)2的约数的个数
AcWing 198. 反素数通过分析元素性质发现可以直接dfs

一、约数个数

本节前半部分需要用到的知识:

由算数基本定理得正整数N可以写作 N = p 1 C 1 × p 2 C 2 × p 3 C 3 ⋯ × p m C m N=p_1^{C_1}\times p_2^{C_2} \times p_3^{C_3} \cdots \times p_m^{C_m} N=p1C1×p2C2×p3C3×pmCm

N的正约数个数为( Π Π Π是连乘积的符号,类似 ∑ ∑ )

( c 1 + 1 ) × ( c 2 + 1 ) × ⋯ ( c m + 1 ) = Π i = 1 m ( c i + 1 ) (c_1+1)\times (c_2+1)\times \cdots (c_m+1)=\Pi_{i=1}^{m}(ci+1) (c1+1)×(c2+1)×(cm+1)=Πi=1m(ci+1)
N的所有正约数和为

( 1 + p 1 + p 1 2 + ⋯ + p 1 c 1 ) × ⋯ × ( 1 + p m + p m 2 + ⋯ + p m c m ) = ∏ i = 1 m ( ∑ j = 0 c i ( p i ) j ) (1+p_1+p_1^2+\cdots +p_1^{c_1})\times\cdots\times(1+p_m+p_m^2+\cdots +p_m^{c_m})=\prod_{i=1}^{m}(\sum_{j=0}^{c_i}(p_i)^j) (1+p1+p12++p1c1)××(1+pm+pm2++pmcm)=i=1m(j=0ci(pi)j)

在这里插入图片描述

在这里插入图片描述

1. AcWing 1291. 轻拍牛头

在这里插入图片描述
因为
如果我们分别求每个数的约数的个数,那么每次的时间复杂度至少是 O ( n ) O(\sqrt n) O(n )的,所以我们没必要每次都一个一个求,我们正着直接求很难,那么我们就倒着求,我们类比线性筛的思路,如果对于一个数x而言,数d是x的约数,那么x一定是d的倍数,所以我们直接筛一遍倍数即可。时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

注意最后需要除去自己

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 1000007;

int n, m;
int a[N];
int primes[N];
int cnt[N];
int S[N];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++ i){
        scanf("%d", &a[i]);
        cnt[a[i]] ++ ;
    }
    
    for(int i = 1; i <= 1e6; ++ i){//nlogn
        for(int j = i; j <= 1e6; j += i){
            S[j] += cnt[i];
        }
    }
    
    for(int i = 1; i <= n; ++ i)
    printf("%d\n", S[a[i]] - 1);//除去它自己
    return 0;
}

2. AcWing 1294. 樱花

在这里插入图片描述
在这里插入图片描述
所以本题实际上转换成了求 n ! 2 n!^2 n!2的约数的个数,我们根据公式,

由算数基本定理得正整数N可以写作 N = p 1 C 1 × p 2 C 2 × p 3 C 3 ⋯ × p m C m N=p_1^{C_1}\times p_2^{C_2} \times p_3^{C_3} \cdots \times p_m^{C_m} N=p1C1×p2C2×p3C3×pmCm

N的正约数个数为( Π Π Π是连乘积的符号,类似 ∑ ∑ )

( c 1 + 1 ) × ( c 2 + 1 ) × ⋯ ( c m + 1 ) = Π i = 1 m ( c i + 1 ) (c_1+1)\times (c_2+1)\times \cdots (c_m+1)=\Pi_{i=1}^{m}(ci+1) (c1+1)×(c2+1)×(cm+1)=Πi=1m(ci+1)

求一个数的约数个数是分解质因数连乘,那么求一个数的阶乘的约数的个数实际上就是求1~n的每个数的约数个数的乘积。

因为 n ! 2 n!^2 n!2太大了,所以我们引入一道题目:我们发现只需要将阶乘分解之后,所有质因子的指数的乘积的二倍既是答案。

2.1 AcWing 197. 阶乘分解

在这里插入图片描述
在这里插入图片描述

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1000010;

int primes[N], cnt;
bool st[N];

void init(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] * i <= n; j ++ )
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int main()
{
    int n;
    cin >> n;
    init(n);

    for (int i = 0; i < cnt; i ++ )
    {
        int p = primes[i];
        int s = 0;
        for (int j = n; j; j /= p) s += j / p;
        printf("%d %d\n", p, s);
    }

    return 0;
}


所以我们只需要在上述代码中稍作修改,累积的是每个质因子的指数的二倍即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>

using namespace std;

const int N = 1000007;
typedef long long ll;
ll mod = 1e9 + 7;

ll n, m, cnt;
int primes[N];
bool vis[N];

void get_primes(ll n)
{
    vis[0] = vis[1] = 1;
    for(int i = 2; i <= n; ++ i){
        if(vis[i] == 0){
            primes[++ cnt] = i;
        }
        for(int j = 1; j <= cnt && i * primes[j] <= n; ++ j){
            vis[i * primes[j]] = true;
            if(i % primes[j] == 0)break;
        }
    }
}

int main(){
    scanf("%lld", &n);
    get_primes(n);
    ll res = 1;
    for(int i = 1; i <= cnt; ++ i){
        int p = primes[i];
        int sum = 0;
        for(int j = n; j; j /= p)
            sum += j / p;
        res = (res * (2 * sum + 1)) % mod;
    }
    cout << res % mod << endl;
    return 0;
}

3. AcWing 198. 反素数

在这里插入图片描述

很重要的一个解决问题的方法:找题目的性质!!!

如果没有思路的话,或者说数论的题目,所给的条件太少了,我们可以找一下题目中各各变量的性质!

在这里插入图片描述
所以我们直接爆搜即可。然后搜的过程中应用那三个我们发现的性质,其中这三个性质在之后的题目中用处也是非常大的。

//我们只用到了最多9个质因子,因为再多乘起来就大于1e9了,所以我们直接打表找到前9个质因子即可。
//我们需要找到的是约数个数最多的最小的数
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;

const int N = 500007;
typedef long long ll;
int primes[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
int n, m;
int maxnum, number;
//使用的是第几个质数,最大次数,乘起来得到的x(反素数备选项),当前约数的个数(我们要找的是约数个数最多的最小的数)
void dfs(int cnt, int last_pow, int mul, int num)
{
    if(num > maxnum || (num == maxnum &&  mul < number)){
        maxnum = num;
        number = mul;
    }
    
    if(cnt == 9)return ;
    
    for(int i = 1; i <= last_pow; ++ i){//当前质因子的次数
        if((ll)primes[cnt] * mul > n) break;
        mul *= primes[cnt];//这个是要修改的
        dfs(cnt + 1, i, mul, num * (i + 1));//约数个数就是Π(ci + 1)
    }
}

int main()
{
    scanf("%d", &n);
    dfs(0, 30, 1, 1);
    cout << number << endl;
    return 0;
}


4. AcWing 200. Hankson的趣味题

在这里插入图片描述

我的标程(能过原题):
但是y总竟然造了一组数据把标程给卡T了…

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>

using namespace std;

const int N = 50007;
int n, m, t, ans;
int a, b, c, d;

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

int gcd(int a, int b)
{
    if(b == 0)return a;
    return gcd(b, a % b);
}

int main()
{
    t = read();
    while(t -- ){
        ans = 0;
        a = read(), b = read(), c = read(), d = read();
        for(int x = 1, y; x * x <= d; ++ x)
        {
            if(d % x == 0){
                if(gcd(x, a) == b && d * gcd(x, c) == x * c){
                    ans ++ ;
                }
                if(x != d / x){
                    y = d / x;
                    if(gcd(y, a) == b && d * gcd(y, c) == y * c){
                        ans ++ ;
                    }
                }
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

在这里插入图片描述
%%%

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 45000, M = 50;

int primes[N], cnt;
bool st[N];

PII factor[M];
int cntf;

int divider[N], cntd;

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

void dfs(int u, int p)
{
    if (u > cntf)
    {
        divider[cntd ++ ] = p;
        return;
    }

    for (int i = 0; i <= factor[u].second; i ++ )
    {
        dfs(u + 1, p);
        p *= factor[u].first;
    }
}

int main()
{
    get_primes(N);

    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a0, a1, b0, b1;
        scanf("%d%d%d%d", &a0, &a1, &b0, &b1);

        int d = b1;
        cntf = 0;
        for (int i = 0; primes[i] <= d / primes[i]; i ++ )
        {
            int p = primes[i];
            if (d % p == 0)
            {
                int s = 0;
                while (d % p == 0) s ++, d /= p;
                factor[ ++ cntf] = {p, s};
            }
        }
        if (d > 1) factor[ ++ cntf] = {d, 1};

        cntd = 0;
        dfs(1, 1);

        int res = 0;
        for (int i = 0; i < cntd; i ++ )
        {
            int x = divider[i];
            if (gcd(x, a0) == a1 && (LL)x * b0 / gcd(x, b0) == b1)
            {

                res ++ ;
            }
        }

        printf("%d\n", res);
    }

    return 0;
}

二、欧拉函数

在这里插入图片描述
线性筛筛欧拉函数的原理

1. AcWing 201. 可见的点

在这里插入图片描述
在这里插入图片描述
用线性筛筛欧拉函数即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<iostream>

using namespace std;
typedef long long ll;
const int N = 500007, M = 1000007, INF = 0x3f3f3f3f;
const double inf = 1e100;
const double eps = 1e-8;
const int mod = 10007;

int primes[N], cnt;
int phi[N];
bool vis[N];

void init(int n)
{
    phi[1] = 1;
    for(int i = 2; i <= n; ++ i){
        if(!vis[i]){
            primes[ ++ cnt] = i;
            phi[i] = i - 1;
        }
        for(int j = 1; j <= cnt && i * primes[j] <= n; ++ j){
            vis[i * primes[j]] = true;
            if(i % primes[j] == 0){//最小的质因子
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            else phi[i *primes[j]] = phi[i] * (primes[j] - 1);
        }
    }

}

int n, m, t, kcase;

int main()
{
    scanf("%d", &t);
    init(N - 1);

    while(t -- ){
        scanf("%d", &n);
        ll ans = 0;
        for(int i = 1; i <= n; ++ i)
            ans += phi[i] * 2;
        printf("%d %d %lld\n",  ++ kcase, n, ans + 1);
    }
    return 0;
}

2. AcWing 220. 最大公约数

在这里插入图片描述
我们可以莫比乌斯反演!

在这里插入图片描述

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>

using namespace std;
typedef long long ll;
const int N = 10000007;

int n, m;
int primes[N];
int phi[N];
int cnt;
bool vis[N];
ll sum[N];

void init(int n)
{
    phi[1] = 0;//这里应该是0
    for(int i = 2; i <= n; ++ i){
        if(vis[i] == 0){
            primes[ ++ cnt] = i;
            phi[i] = i - 1;
        }
        for(int j = 1; j <= cnt && primes[j] * i <= n; ++ j){
            vis[i * primes[j]] = true;
            if(i % primes[j] == 0){
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }

    for(int i = 1; i <= n; ++ i)//求phi[n]的前缀和
        sum[i] = sum[i - 1] + phi[i];
}

int main()
{
    scanf("%d", &n);
    init(n);

    ll ans = 0;
    for(int i = 1; i <= cnt; ++ i){
        int p = primes[i];
        ans += sum[n / p] * 2 + 1;
    }
    printf("%lld\n", ans);
    return 0;
}

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页