AcWing 198. 反素数

\(AcWing\) \(198\). 反素数

一、题目描述

对于任何正整数 \(x\),其约数的个数记作 \(g(x)\),例如 \(g(1)=1、g(6)=4\)

如果某个正整数 \(x\) 满足:对于任意的小于 \(x\) 的正整数 \(i\),都有 \(g(x)>g(i)\),则称 \(x\) 为反素数。

例如,整数 \(1,2,4,6\) 等都是反素数。

现在给定一个数 \(N\),请求出不超过 \(N\) 的最大的反素数。

输入格式
一个正整数 \(N\)

输出格式
一个整数,表示不超过 \(N\) 的最大反素数。

数据范围
\(1≤N≤2∗10^9\)

输入样例

1000

输出样例

840

二、解题思路

1、前置知识

\(N\)的唯一分解式:

\[N=P_1^{c_1}P_2^{c_2}...P_k^{c_k} \]

知识总结

  • 约数个数公式
    \(d(N)=(c_1+1)\times (c_2+1)\times ... \times (c_k+1)\)

举栗子

\(180=2^2∗3^2∗5\)
约数个数=\((1+2)∗(1+2)∗(1+1)=18\)

  • 约数和公式
    \(\sigma(n)=({p_1}^{0}+{p_1}^{1}+{p_1}^{2}+...+{p_1}^{c_1})({p_2}^{0}+{p_2}^{1}+{p_2}^{2}+...+{p_2}^{c_2})...({p_k}^{0}+{p_k}^{1}+{p_k}^{2}+...+{p_k}^{c_k})\)
    Sigma(大写Σ,小写σ),是第十八个希腊字母。

举栗子

\(180= 2^2 * 3^2 * 5\)
约数和\(=(1+2+4) * (1+3+9 ) * (1+5)=546\)

2、理解反素数

说白了,素数之所以称为素数,就是因为约数少,只有\(1\)和自己。
反素数,也就可以理解为 :约数多,而且每个数的约数个数,都比小于自己的数字约数个数多。

3、反素数性质1

\(1\sim N\)中最大的 反素数,就是 \(1\sim N\)中约数个数最多的数中最小的一个

  • \(g(x)\)=约数个数,求最大的反素数,不就是在求哪个数的约数个数最多嘛~
  • 如果不是最小的那一个,必然会出现g(x)=g(i),与反素数的定义就矛盾了~

4、反素数性质2

联想约数个数公式:
\(d(N)=(c_1+1)\times (c_2+1)\times ... \times (c_k+1)\)

一个数的约数个数,不与具体的质数因子相关,只与质数因子的幂次相关!
换言之,由于我们追求最小的数字,那么根据 贪心思想 ,我们知道:质因子越小,幂次越大,才可以保障最终的数字最小!!!
\(c_1>=c_2>=c_3>=...>=c_k\)

举个栗子

假设\(t=2^{c_1}×3^5×5^4×7^{c_4}…\)
\(c_1>=5>=4>=c_4\),交换\(3\)\(5\)的次数即变成\(t=2^{c_1}×3^4×5^5×7^{c_4}…\),这样交换约数个数相同,可\(3^4∗5^5 > 3^5∗5^4\),交换后该数变大了

5、反素数性质3

  • \(1\sim N\)中任何数的不同质因子都不会超过\(9\)
    因为\(2 \times 3\times 5\times 7\times 11\times 13\times 17\times 19\times 23\times 29 >2\times 10^9\):
#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
int main() {
    cout << INT_MAX << endl;
    LL s = 1;
    for (int i = 0; i <= 8; i++) s *= primes[i];
    cout << s << endl;
    s = 1;
    for (int i = 0; i <= 9; i++) s *= primes[i];
    cout << s << endl;
    return 0;
}

输出结果:

2147483647
223092870
6469693230

  • 所有质因数的指数总和最大不超过\(30\)
    因为\(2^{31}>2\times 10^9\).

根据上面一系列性质,我们得出了最简洁的思路,使用\(dfs\),尝试确定前九个质数的指数,然后满足指数单调递减,总乘积不超过\(N\),且同时记录约数的个数。

三、由小到大枚举

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
const int N = 2e9;

int n;
int primes[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23}; // 枚举的质数前9位,足够了
int mc, mx;                                       // 最大反素数的约数个数,最大反素数值

/*
u:走到数组primes的第几位
last:前一个质数,我们取的指数是多少,当前位置取的质数的指数需要小于等于last
tmx: 当前路线上取得的最大反素数
tmc: 当前路线上取得的约数个数
*/
void dfs(int u, int last, int tmx, int tmc) {
    if (u == 9) { // 走完全程
        // ① 当前路径中约数个数大于最大约数个数,需要替换
        // ② 当前路径中的约数个数等于最大约数个数,但是,当前路径的数值更小,也需要替换
        if (tmc > mc || (tmc == mc && tmx < mx))
            mc = tmc, mx = tmx;
        return;
    }

    for (int i = 0; i <= last; i++) { // 幂次只能是小于等于前一个数的幂次
        int k = pow(primes[u], i);
        if (k * tmx > n) break; // 如果取当前这个素数的i次方,乘到原来的最大值后,大于n,那么此路径不通

        /*
        u+1:下一个素数位置
        i: 当前u这个素数取了i次幂
        tmx*k : 因为取了i次幂,所以,最大值变大了k倍
        tmc*(i+1) : 根据约数个数公式,因为有了质数 primes[u]后,取了i次幂,那么约数和增加(i+1)倍
        */
        dfs(u + 1, i, tmx * k, tmc * (i + 1));
    }
}

signed main() {
    cin >> n;
    dfs(0, 30, 1, 1); // 从最小的质数2出发,最大的幂次不超过30
    // n的最小取值是1,也就是小于等于1的范围内,最小的反素数是1,而且,约数的个数也是1
    cout << mx << endl;
}

四、经验总结

INT_MAX范围内,最大的反素数拥有的约数个数是\(1600\) ,这个是整数范围内约数个数的极限值, 需要记住这个值,后面的做题当中可以当做常数使用!

输入\(2e9\),输出\(1536\)
输入INT_MAX,即\(2147483647\),输出\(1600\)

posted @ 2022-05-20 14:17  糖豆爸爸  阅读(113)  评论(0编辑  收藏  举报
Live2D