zoukankan      html  css  js  c++  java
  • 质数与约数

    质数与约数

    1. 算法分析

    1.1 基本概念

    质数: 作为最小单位,无法在进行拆分
    约数: 8的约数有1,2,4,8

    1.2 常用公式和性质

    1.2.1 质数

    重要结论

    1. int范围内最多只有10个不重复质因子,且所有质数的次数总和不能超过30
    2. 素数的大小:x/lnx,素数的个数:xlnx
    3. 任何一个合数n一定存在一个<=sqrt(n)的质因子

    1.2.2 约数

    常用公式

    1. n的约数个数: 对于一个大于1正整数可以分解质因数:n=(p1 ^ a1) * (p2 ^ a2) * ... *(pk ^ ak),则n的正约数个数为:f(n) = (a1+1)(a2+1)...(ak+1)
    2. n的约数和:对于一个大于1正整数n可以分解质因数:n=(p1 ^ a1) * (p2 ^ a2) * ... *(pk ^ ak),那么n的(a₁+1)(a₂+1)(a₃+1)…(ak+1)个正约数的和为:f(n)=(p1 ^ 0 + p1 ^ 1 + p1 ^ 2 + … + p1 ^ a1)(p2 ^ 0 + p2 ^ 1 + p2 ^ 2+ … + p2 ^ a2)…(pk ^ 0 + pk ^ 1 + pk ^ 2 +…+ pk ^ ak),其中:计算每个(pi ^ 0 + pi ^ 1 + pi ^ 2 + … + pi ^ ai)时可以借助秦九韶公式优化运算
    3. 1~N中所有数的约数个数和
      N/1+N/2+...+N/N = N(lnN + C) ,C= 0.57721566490153286060651209
    4. 1~N中所有数的约数和:
      1 * N / 1 + 2 * N / 2 + ... + N * N / N

    重要结论

    1. int范围内拥有最多约数的那个数字拥有1600个约数
    2. 1~N中任何数的不同质因子不会超过10个,且所有质因子的指数总和不超过30

    2. 板子

    2.1 质数

    2.1.1 素数判定

    1. 试除法判定素数(小素数)
    // 判断是否为素数
    bool is_prime(int x)
    {
        if (x < 2) return false;  // 1不是素数
        for (int i = 2; i <= x / i; ++i)  // 从2循环到sqrt(x),但是循环进行条件不写i <= sqet(x),因为太慢;不写i * i <= x,因为可能有溢出的风险
            if (x % i == 0) return false;
        return true;
    }
    
    1. miller-rabin判断素数(大素数)
    #include <bits/stdc++.h>
    using namespace std ;
    typedef unsigned long long ll;
    
    ll multi(ll a, ll b, ll mod) {
        ll ret = 0;
        while(b) {
            if(b & 1) ret = ret + a;
            if(ret >= mod) ret -= mod;
    
            a = a + a;
            if(a >= mod) a -= mod;
            b >>= 1;
        }
        return ret;
    }
    
    ll quick_pow(ll a, ll b, ll mod) {
        ll ret = 1;
        while(b) {
            if(b & 1) ret = multi(ret, a, mod);
            a = multi(a, a, mod);
            b >>= 1;
        }
        return ret;
    }
    
    bool Miller_Rabin(ll n) {
        ll u = n - 1, pre, x;
        int i, j, k = 0;
        if(n == 2 || n == 3 || n == 5 || n == 7 || n  == 11) return true;
        if(n == 1 || (!(n % 2)) || (!(n % 3)) || (!(n % 5)) || (!(n % 7)) || (!(n % 11)))
            return false;
        for(; !(u & 1); k++, u >>= 1);
        srand(time(NULL));
        for(i = 0; i < 5; i++) {
            x = rand() % (n - 2) + 2;
            x = quick_pow(x, u, n);
            pre = x;
            for(j = 0; j < k; j++) {
                x = multi(x, x, n);
                if(x == 1 && pre != 1 && pre != (n - 1))
                    return false;
                pre = x;
            }
            if(x != 1) return false;
        }
        return true;
    }
    

    2.1.2 质因数分解

    1. 试除法分解质因数(小整数) : O(sqrt(x))
    int x;
    cin >> x;
    for (int i = 2; i <= x / i; ++i)  // 因为一个数x的所有因子内只会有一个大于sqrt(x), 所以只要枚举到sqrt(x),而后单独判断大于sqrt(x)的那个即可
    {
        if (x % i == 0) // 如果能够整除
        {
            int s = 0;  // 记录能够整除几次
            while (x % i == 0)
            {
                s++;
                x /= i;
            }
            printf("%d %d
    ", i, s);  // 输出因子和次数
        }
    }
    if (x > 1) printf("%d %d
    ", x, 1); // 如果有剩下的数字必然比sqrt(x)大,因为如果比sqrt(x)小,那么在做上一步的枚举时就已经被除掉了,一旦有,就必然次数为1 
    
    1. 更快的质因数分解:先筛素数,然后拿素数判断是不是质因数 O(sqrt(x/lnx))
    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 main() {
        ...
        for (int i = 0; prime[i] <= t / prime[i]; ++i)
        {
            int p = prime[i];
            if (t % p == 0)
            {
                int s = 0;
                while (t % p == 0)
                {
                    s++;
                    t /= p;
                }
                factor[fcnt++] = {p, s};
            }
        }
        if (t > 1) factor[fcnt++] = {t, 1};
        ...
    }
    
    
    1. pollard_rho分解质因数(大整数)
    typedef long long LL;
    const int maxn = 10000;
    const int S = 20;
    LL factor[maxn];
    int tot;  // 记录有几个质因子
    
    //返回(a * b) % c,a,b,c<2^63
    LL multi_mod(LL a, LL b, LL c)
    {
        a %= c;
        b %= c;
        LL ret = 0;
        while(b)
        {
            if (b & 1)
            {
                ret += a;
                if (ret >= c) ret -= c;
            }
            a <<= 1;
            if(a >= c) a -= c;
            b >>= 1;
        }
        return ret;
    }
    
    //返回x^n mod c ,非递归版
    LL pow_mod(LL x, LL n, LL mod)
    {
        LL ret = 1;
        while(n)
        {
            if(n & 1) ret = multi_mod(ret, x, mod);
            x = multi_mod(x, x, mod);
            n >>= 1;
        }
        return ret;
    }
    
    //以a为基,n-1=x*2^t,检验n是不是合数
    bool check(LL a, LL n, LL x, LL t)
    {
        LL ret = pow_mod(a, x, n), last = ret;
        for (int i = 1; i <= t; i++)
        {
            ret = multi_mod(ret, ret, n);
            if (ret == 1 && last != 1 && last != (n-1)) return 1;
            last = ret;
        }
        if (ret != 1) return 1;
        return 0;
    }
    
    /*素数测试*/
    bool Miller_Rabin(LL n)
    {
        LL x = n - 1, t = 0;
        while ((x & 1) == 0) x >>= 1, t++;
        bool flag = 1;
        if (t >= 1 && (x & 1) == 1)
        {
            for(int k = 0; k < S; k++)
            {
                LL a = rand()%(n-1) + 1;
                if (check(a, n, x, t))
                {
                    flag = 1; break;
                }
                flag = 0;
            }
        }
        if (!flag || n == 2) return 0;
        return 1;
    }
    
    LL gcd(LL a,LL b)
    {
        if (a == 0) return 1;
        if (a < 0) return gcd(-a, b);
        while (b)
        {
            LL t = a % b;
            a = b;
            b = t;
        }
        return a;
    }
    
    /*大整数素因子分解*/
    LL Pollard_rho(LL x, LL c)
    {
        LL i = 1, x0 = rand() % x, y = x0, k = 2;
        while(1)
        {
            i++;
            x0 = (multi_mod(x0, x0, x) + c) % x;
            LL d = gcd(y - x0, x);
            if (d != 1 && d != x)
            {
                return d;
            }
            if (y == x0) return x;
            if (i == k)
            {
                y = x0;
                k += k;
            }
        }
    }
    
    //递归进行质因数分解N
    // 输入n为要质因数分解的大整数,质因数分解完后tot为质因数个数,factor存储分解后得到的质因数
    // 使用pollard_rho随机算法
    void findfac(LL n)
    {
        if (!Miller_Rabin(n))
        {
            factor[tot++] = n;  
            return;
        }
        LL p = n;
        while(p >= n) p = Pollard_rho(p, rand() % (n-1) + 1);
        findfac(p);
        findfac(n / p);
        return ;
    }
    
    1. 阶乘分解
    /*
    把阶乘n!分解的算法:
    1.求出n的所有质因数(欧拉筛法)
    2.计算每个质因数的次数
    
    原理:
    1.所有的质因子有哪些?
    对于n来说,如果n=(p1^a1)*(p2^a2)*...*(pn^an)
    那么1~n-1最大的质数个数也只有n个
    所有对n做个筛质因数就能得到n!的所有质因子pi
    2.质因子的次数?
    从每个质因子的角度考虑,如果某个t属于[1,n],一旦p1|t,那么将对a1贡献1;
    如果(p1)^2|t,那么将对a1贡献2;所有只需要把n/p1+n/(p1**2)+...+n/n加起来即可
    */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 1e6 + 10;
    int prime[N], cnt;
    int st[N];
    
    // 筛素数
    void init(int n)
    {
        for (int i = 2; i <= n; ++i)
        {
            if (!st[i]) prime[cnt++] = i;
            for (int j = 0; prime[j] <= n / i; ++j)
            {
                st[prime[j] * i] = 1;
                if (i % prime[j] == 0) break;
            }
        }
    }
    
    int main()
    {
        int n;
        cin >> n;
        init(n);
        for (int i = 0; i < cnt; ++i)
        {
            int p = prime[i];  // 每个质数
            int s = 0;  // 计算次数
            for (int j = n; j; j /= p) s += j / p;
            printf("%d %d
    ", p, s);
        }
        return 0;
    }
    

    2.1.3 筛素数

    1. 埃氏筛法
    // 埃氏筛法
    void get_prime(int n)
    {
        for (int i = 2; i <= n; ++i)  // 枚举从2~n的所有数
        {
            if (!st[i])  // 如果这个数字没有被筛过(这个数字必为素数)
            {
                prime[cnt++] = i;  // 记录这个素数
                for (int j = i + i; j <= n; j += i) st[j] = 1;  // 把素数的所有倍数都筛掉
            }
        }
    }
    
    1. 线性筛(欧拉筛)
    // 线性筛素数(以下把prime[j]这个数简写为pj)
    // 如何保证线性:一个合数只能被筛掉一次
    // 如何保证一个数字只被筛掉一次:这个数字只被它的最小质因数筛掉,当它的其他质因数想要筛掉它时,将无法进行筛除操作
    // 可以证明:pj是pj*i的最小质因数:
    // ①假设i % pj == 0, 因为pj从小到大枚举,所以pj是i的最小质因数,所以pj是pj*i的最小质因数
    // ②假设i % pj != 0, 则pj比i的最小质因数还要小,则pj是pi*i的最小质因数
    // 综上,无论是哪种情况,pj都是pj*i的最小质因数
    
    // 假设pj*i会被多次筛掉,那么将多次执行st[prime[j] * i] = true;语句,而从第二次执行该语句后,i比之前大,
    // 那么pj比之前的小,然而之前的pj已经是最小质因数,矛盾,假设不成立
    void get_prime(int n)
    {
        for (int i = 2; i <= n; ++i)
        {
            if (!st[i]) prime[cnt++] = i; // 如果这个数字没有被记录,那么这个数字必然为素数,记录一下
            // pj枚举到n/i是因为:pj小于等于n/i才能保证pj*i小于等于n
            for (int j = 0; prime[j] <= n/i; ++j)  
            {
                st[prime[j] * i] = true;  // 筛掉pj*i这个合数
                if (i % prime[j] == 0) break;      
            }                
        }
    }
    

    2.2 约数

    2.2.1 求x相关

    1. 求x的约数(O(sqrt(x)))
    // 试除法求约数
    vector<int> get_divisors(int x)
    {
        vector<int> res;  // 记录答案
        for (int i = 1; i <= x /i; ++i)  // 枚举到sqrtx(x)
        {
            if (x % i == 0)  // 如果能够整除
            {
                res.push_back(i);  // 放入i
                if (i != x / i) res.push_back(x / i);  // x/i不等于i,也放入答案中
            }
        }
        sort(res.begin(), res.end());  // 排序
        return res;
    }
    
    1. 求x的约数个数(O(sqrt(x)))
        map<int, int> prime;  // 记录质数,使用map是因为如果使用数组一会遍历时会把不是质数的也遍历进去
        while (n--)  // 读入n个数
        {
            int a;
            scanf("%d", &a);
            for (int i = 2; i <= a / i; ++i)  // 对每一个求质因数
            {
                while (a % i == 0)
                {
                    prime[i]++;  // 记录每个质数出现次数
                    a /= i;
                }
            }
            if (a > 1) prime[a] ++;  // 记录大于sqrt(a)的那个质数的次数
        }
        unsigned long long res = 1;
        for (auto p: prime) res = res * (p.second + 1) % mod;  // 约数个数定理
    
    1. 求x的约数和(O(sqrt(x)))
        // 计算出每个质数的次数
        while(n--)  
        {
            int a;
            scanf("%d", &a);
            for (int i = 2; i <= a / i; ++i)
            {
                while (a % i == 0)
                {
                    primes[i] ++;
                    a /= i;
                }
            }
            if (a > 1) primes[a] ++;
        }
        PLL res = 1;
        // 使用约数和定理,这里借助秦九韶公式计算
        for (auto pi: primes)
        {
            int p = pi.first, a = pi.second;  // p为质因数,a为质因数出现的次数
            PLL t = 1;
            while(a--) t = (p * t + 1) % mod ;
            res = res * t % mod;
        }
    

    2.2.2 求1~N相关

    1. 求1~N中所有数的约数(O(nlnn))
    vector<int> factor[500010];
    for (int i = 1; i <= n; ++i) 
        for (int j = 1; j <= n / i; ++j)
            factor[i * j].push_back(i);
    
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j < factor[i].size(); ++j)
            printf("%d ", factor[i][j]);
    
    1. 求1~N中所有数的约数个数和(O(n))
    sum = 0;
    for(i=1;i<=n;i++)
        sum += n / i;
    printf("n = %lld
    ",n);
    printf("%lld
    ",sum);
    
    1. 求1~N中所有数的约数和(O(n))
    sum = 0;
    for(i=1;i<=n;i++)
        sum += i * (n / i);
    printf("n = %lld
    ",n);
    printf("%lld
    ",sum);
    

    2.1.3 gcd

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

    3. 例题

    acwing1291轻拍牛头
    共有 N 个整数 A1,A2,…,AN,对于每一个数 Ai,求其他的数中有多少个是它的约数。

    // i 是所有i的倍数的约数
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 1e6 + 10;
    int a[N], cnt[N], n, res[N];
    
    int main() {
        cin >> n;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            cnt[a[i]] ++;
        }
        
        for (int i = 1; i <= N; ++i) {
            if (!cnt[i]) continue;
            for (int j = 1; j * i <= N; ++j) {
                if (!cnt[j * i]) continue;
                res[j * i] += cnt[i];
            }
        }
        
        for (int i = 1; i <= n; ++i) cout << res[a[i]] - 1 << endl;
        return 0;
    }
    

    acwing1294樱花

    /* 从题意可以知道x和y都大于n!
    设y=n!+k(k∈N∗)
    1/x+1/(n!+k)=1/n!
    化简得:x=(n!)**2 / k + k
    则x的个数就是(n!)**2的约束个
    然后 约数个数和+阶乘分解 处理 */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    int const N = 1e6 + 10, MOD = 1e9 + 7;
    int prime[N], cnt;
    int st[N];
    
    // 筛素数
    void init(int n)
    {
        for (int i = 2; i <= n; ++i)
        {
            if (!st[i]) prime[cnt++] = i;
            for (int j = 0; prime[j] <= n / i; ++j)
            {
                st[prime[j] * i] = 1;
                if (i % prime[j] == 0) break;
            }
        }
    }
    
    int main()
    {
        int n;
        cin >> n;
        init(N);
        int res = 1;
        for (int i = 0; i < cnt; ++i)
        {
            int p = prime[i];  // 每个质数
            int s = 0;  // 计算次数
            for (int j = n; j; j /= p) s += j / p;
            res = res * (LL)(s * 2 + 1) % MOD;
        }
        cout << res;
        return 0;
    }
    

    acwing198反素数
    对于任何正整数x,其约数的个数记作g(x),例如g(1)=1、g(6)=4。
    如果某个正整数x满足:对于任意的小于x的正整数 i,都有g(x)>g(i) ,则称x为反素数。
    例如,整数1,2,4,6等都是反素数。
    现在给定一个数N,请求出不超过N的最大的反素数。
    N~1e9

    // 推导可知:反素数必须满足
    // 1. 不同质因子数目最多9个
    // 2. 所有质因子次数总和小于等于31
    // 3. 质因子次数单调不增
    // 从以上几个条件可以看出,dfs的空间其实很小
    #include <bits/stdc++.h>
    
    typedef long long LL;
    using namespace std;
    
    int ans, maxv, n, pirme[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};  // 最多指数只能有9个
    
    // u当前的质数下标,last上一次的最大次数,sum_all总和,sum_fac约数个数和, sum_c次数和
    void dfs(int u, int last, int sum_all, int sum_fac, int sum_c) {
        if (sum_fac > maxv || (sum_fac == maxv && sum_all < ans)) {  // 如果当前约数个数和最多或者当前总和更小
            maxv = sum_fac;
            ans = sum_all;
        }
        if (u >= 9 || sum_c >= 31) return;
        
        int fact = 1;
        for (int i = 0; i <= last; ++i) {  // 每个质数取i次
            if ((LL)sum_all * fact > n) continue;  // 如果乘完大于n
            if (sum_c + i >= 31) continue;
            dfs(u + 1, i, sum_all * fact, sum_fac * (i + 1), sum_c + i);
            fact *= pirme[u];  
        }
    }
    
    int main () {
        cin >> n;
        dfs(0, 30, 1, 1, 0);
        cout << ans;
        return 0;
    }
    

    acwing200 Hankson的趣味题
    n组测试样例,每组测试样例输入:a0, a1, b0, b1
    求出有多少个x满足,(x, a0)=a1, [x, b0]=b1
    n~2000, a0、a1、b0、b1~2e9

    /*
    本题需要找出x使得(x, a0)=a1, [x, b0] = b1
    从上面的关系可以看出:x是b1的因子,因此我们考虑枚举b1的所有因子
    现在如何快速获取b1的所有因子呢?
    可以先对b1做质因子分解,然后dfs暴搜每个质因子的次数即可
    分析以下这样做的时间复杂度:
    质因子分解的复杂度为: sqrt(x/lnx)
    dfs一共只有1536种情况,可以看出搜索空间非常小
    */
    #include<bits/stdc++.h>
     
    using namespace std;
    
    int n;
    int const N = 5e5 + 10;
    int prime[N], cnt, st[N];
    struct Fac
    {
        int p, s;
    }factor[1600];
    int fcnt;
    int a0, a1, b0, b1;
    int divisor[N], dcnt;
    
    // 欧拉筛法
    void init(int n)
    {
        for (int i = 2; i <= n; ++i)
        {
            prime[cnt++] = i;
            for (int j = 0; prime[j] <= n / i; ++j)
            {
                st[prime[j] * i] = 1;
                if (i % prime[j] == 0) break;
            }
        }
    }
    
    //暴搜每个质因子的次数得到b1的因子
    void dfs(int u, int p)
    {
        if (u == fcnt)
        {
            divisor[dcnt++] = p;
            return;
        }
        for (int i = 0; i <= factor[u].s; ++i)
        {
            dfs(u + 1, p);
            p *= factor[u].p;
        }
    }
     
    int main()
    {
        init(N - 1);
        cin >> n;
        while (n--)
        {
            dcnt = fcnt = 0;
            cin >> a0 >> a1 >> b0 >> b1;
    
            // 把b1做质因子分解
            int t = b1;
            for (int i = 0; prime[i] <= t / prime[i]; ++i)
            {
                int p = prime[i];
                if (t % p == 0)
                {
                    int s = 0;
                    while (t % p == 0)
                    {
                        s++;
                        t /= p;
                    }
                    factor[fcnt++] = {p, s};
                }
            }
            if (t > 1) factor[fcnt++] = {t, 1};
    
            // dfs暴搜出b1的因子
            dfs(0, 1);
    
            // 统计出答案个数
            int res = 0;
            for (int i = 0; i < dcnt; ++i)
            {
                int x = divisor[i];
                if (__gcd(a0, x) == a1 && (long long)x * b0 / __gcd(x, b0) == b1) res++;
            }   
            cout << res << endl;
        }
        
        return 0;
    }
    
  • 相关阅读:
    Oracle安装
    自动化测试开发
    Hyperf 接入阿里云ACM应用配置管理中心
    PHP redis有序集合实现分页
    虚拟现实与大数据
    树形数据结构化
    立即执行函数
    jQuery中的显示与隐藏
    jQuery中的HTML
    Django+Nginx配置+前后端交互
  • 原文地址:https://www.cnblogs.com/spciay/p/13047716.html
Copyright © 2011-2022 走看看