Contest100000592 - 《算法笔记》5.5小节——数学问题->质因子分解

旧城冷巷雨未停 提交于 2020-01-13 05:13:44

常用模板

求所有质因子

fac数组中存放的就是质因子分解的结果,时间复杂度是O(n)O(\sqrt n)
定义如下:

struct factor
{
    int x, cnt; // x为质因子,cnt为其个数
} fac[10];

int prime[maxn], num = 0;

核心代码:

for (int i = 0; i <= sqrt(n); ++i)
{
    if (n % prime[i] == 0)
    {
        fac[num].x = prime[i]; // 记录该因子
        fac[num].cnt = 0;
        while (n % prime[i] == 0) // 计算出质因子prime[i]的个数
        {
            ++fac[num].cnt;
            n /= prime[i];
        }
        ++num; // 不同质因子个数加1
    }
}
if (n != 1) // 如果无法被根号n以内的质因子除尽
{
    fac[num].x = n; // 那么一定有一个大于根号n的质因子
    fac[num++].cnt = 1;
}

最后指出,如果要求一个正整数N的因子个数,只需要对其质因子分解,得到各质因子pip_i的个数分别为e1e2 eke_1、e_2、···\ e_k,于是N的因子个数就是(e1+1)(e2+1)(ek+1)(e_1 + 1)*(e_2 + 1)*···*(e_k + 1)。原因是,对每个质因子pip_i都可以选择器出现0次、1次、··· 、eie_i次,共ei+1e_i + 1种可能,组合起来就是答案。而由同样的原理可知,N的所有因子之和为(1+p1+p12++p1e1)(1+p2+p22++p2e2)(1+pk+pk2++pkek)=1p1e1+11p11p2e2+11p21pkek+11pk(1 + p_1 + p_1^2+···+p_1^{e_1})*(1 + p_2 + p_2^2+···+p_2^{e_2})*···*(1 + p_k + p_k^2+···+p_k^{e_k})\\ =\frac{1-p_1^{e_1 + 1}}{1 -p_1}*\frac{1-p_2^{e_2 + 1}}{1 -p_2}*···*\frac{1-p_k^{e_k + 1}}{1 -p_k}


问题 A && 问题B: 完数

题目描述

求1-n内的完数,所谓的完数是这样的数,它的所有因子相加等于它自身,比如6有3个因子1,2,3,1+2+3=6,那么6是完数。即完数是等于其所有因子相加和的数。

输入

测试数据有多组,输入n,n数据范围不大。

输出

对于每组输入,请输出1-n内所有的完数。如有案例输出有多个数字,用空格隔开,输出最后不要有多余的空格。

样例输入

6

样例输出

6

Note

完数的定义是:如果一个数恰好等于它所有因子之和,则称该数为“完全数”。要注意,只要是因子就行,比如28的所有因子是1、2、4、7、14,相加刚好为28。注意不要当成质因子的题目处理了。
思路就是,用 i 枚举2 ~ n内的所有数,然后 j 枚举2 ~ i内的所有数,如果 j 能被 i 整除就说明 j 是因子,因子和sum += j即可。
因为这是一个时间复杂度为O(n2)O(n^2)的算法,感兴趣的同学试试能不能降低时间复杂度呢?联想一下欧拉筛法怎么做的呢?代码我就不贴了,自己想一下哦~

#include <iostream>
using namespace std;

int main()
{
    int n, sum, flag;
    while (cin >> n)
    {
        flag = 0; // flag = 0表明还没有输出过完数
        for (int i = 2; i <= n; ++i) // 由题目可知1不算在完数内
        {
            int m = i, sum = 1; // 每个数都至少有个因子1,所以初始化因子和蔚1
            for (int j = 2; j < i; ++j) // 枚举1~i的数,是因子则加到sum上,本身除外
                if (m % j == 0) // 如果j是m的因子,则加到sum上去
                    sum += j;
            if (sum == i)
            {
                if (flag)
                    cout << ' ';
                cout << i;
                flag = 1; // 输出过完数了,flag改为1
            }
        }
        cout << endl;
    }

    return 0;
}

问题 C: 质因数的个数

题目描述

求正整数N(N>1)的质因数的个数。
相同的质因数需要重复计算。如120=22235,共有5个质因数。

输入

可能有多组测试数据,每组测试数据的输入是一个正整数N,(1<N<10^9)。

输出

对于每组数据,输出N的质因数的个数。

样例输入

120
200

样例输出

5
5

Note

注意1不是N的质因数;若N为质数,则N是N的质因数。

#include <iostream>
#include <cmath>
using namespace std;

const int maxn = 100001;
int prime[maxn], pNum = 0;
bool p[maxn] = {0};

void FindPrime()
{
    for (int i = 2; i < maxn; ++i)
    {
        if (p[i] == 0)
        {
            prime[pNum++] = i;
            for (int j = i; j < maxn; j += i)
                p[j] = 1;
        }
    }
}

int main()
{
    FindPrime();
    int n, cnt;
    while (cin >> n)
    {
        cnt = 0;
        int sqr = (int)sqrt(1.0 * n);
        for (int i = 0; i < pNum && prime[i] <= sqr; ++i) // 枚举0~sqr的质数
        {
            if (n % prime[i] == 0)
            {
                while (n % prime[i] == 0) // 计算出质因子prime[i]的个数
                {
                    n /= prime[i];
                    ++cnt;
                }
            }
            if (n == 1)
                break;
        }
        if (n != 1) // n不为1,说明没有n的因子,也就说明n是质数
            ++cnt;
        cout << cnt << endl;
    }

    return 0;
}

问题 D: 约数的个数

题目描述

输入n个整数,依次输出每个数的约数的个数。

输入

输入的第一行为N,即数组的个数(N<=1000)
接下来的1行包括N个整数,其中每个数的范围为(1<=Num<=1000000000)
当N=0时输入结束。

输出

可能有多组输入数据,对于每组输入数据,
输出N行,其中每一行对应上面的一个数的约数的个数。

样例输入

6
1 4 6 8 10 12
0

样例输出

1
3
4
4
4
6

Note

按照正常的做法,即对输入的每一个数都去枚举从1~n个数,查看是否是它的因子,如果是则计数加1。这样做可以做,但是时间复杂度将达到i=0n1nmi\sum_{i = 0}^{n - 1}n * m_i,当输入的n接近1000,其中的整数接近10910^9时运行时间会非常非常长,题目直接运行超限。所以降低时间复杂度才是这题的关键。

当然,一般来说降低时间复杂度有两种办法:空间换时间或者优化算法。空间换时间适用于需要进行大量的重复操作时,将重复的操作换成某种意义上只需要一次操作即能解决的问题,典型代表就是素数表。优化算法是指通过针对算法不足的部分,以其他手法改进或提升运行效率。

熟练的同学就知道这题降低时间复杂度的点在哪儿:

  1. 无需遍历1~n,只需遍历到sqrt(n);
  2. 因为因数是成对出现的,比如12,枚举到因数2时就知道,还有一个因数6,所以计数直接加2。有的同学可能会担心后面又枚举到6怎么办?不用担心,12\sqrt 12小于6。原理在于一个数n分解为两个因数的乘积时,其中一个必定小于n\sqrt n,另一个必定大于n\sqrt n。所以我们只要找到所有在n\sqrt n前的因数即可。
  3. 还有一种特殊情况要处理,那就是当n开根号后也是一个整数时,这时它的两个因数都是它的根号,再计数加2就多加一次了,所以cnt要减1。
  4. 对运行时间很敏感的题目不要用cin和cout。
#include <cstdio>
#include <cmath>

int main()
{
    int n, m, cnt;
    while (scanf("%d", &n), n)
    {
        while (n--)
        {
            scanf("%d", &m);
            int sqr = sqrt(m * 1.0), cnt = 0;
            for (int i = 1; i <= sqr; ++i)
                if (m % i == 0)
                    cnt += 2;
            if (sqr * sqr == m)
                --cnt;
            printf("%d\n", cnt);
        }
    }

    return 0;
}

问题 E: 完数与盈数

题目描述

一个数如果恰好等于它的各因子(该数本身除外)子和,如:6=3+2+1,则称其为“完数”;
若因子之和大于该数,则称其为“盈数”。求出2 到60 之间所有“完数”和“盈数”,并以如
下形式输出: E: e1 e2 e3 …(ei 为完数) G: g1 g2 g3 …(gi 为盈数)

输入

输出

按描述要求输出(注意EG后面的冒号之后有一个空格)。

#include <iostream>
#include <cmath>
using namespace std;

int main()
{
    int n, sum, E[10], G[10], cnt1 = 0, cnt2 = 0;
    for (int i = 2; i <= 60; ++i) // 由题目可知1不算在完数内
    {
        int sum = 1; // 每个数都至少有个因子1,所以初始化因子和为1
        for (int j = 2; j < i; ++j) // 枚举1~i的数,是因子则加到sum上,本身除外
            if (i % j == 0) // 如果j是i的因子,则加到sum上去
                sum += j;
        if (sum == i)
            E[cnt1++] = i;
        else if (sum > i)
            G[cnt2++] = i;
    }

    cout << "E:";
    for (int i = 0; i < cnt1; ++i)
        cout << ' ' << E[i];
    cout << " G:";
    for (int i = 10; i < cnt2; ++i)
        cout << ' ' << G[i];

    return 0;
}

一定要自己写一遍哦~~~

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!