##[$AcWing$ $198$. 反素数](https://www.acwing.com/problem/content/description/200/) ### 一、题目描述 对于任何正整数 $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$ **输入样例**: ```cpp {.line-numbers} 1000 ``` **输出样例**: ```cpp {.line-numbers} 840 ``` ### 二、解题思路 #### 1、前置知识 设$N$的唯一分解式: $$N=P_1^{c_1}P_2^{c_2}...P_k^{c_k}$$ [知识总结](https://www.cnblogs.com/littlehb/p/15214618.html) * 约数个数公式 $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$: ```cpp {.line-numbers} #include 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$,且同时记录约数的个数。 ### 三、由小到大枚举 ```cpp {.line-numbers} #include 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$。