0
点赞
收藏
分享

微信扫一扫

AcWing 198. 反素数

Raow1 2022-05-23 阅读 134

AcWing 198. 反素数

​​题目传送门​​

吐槽

这\(NM\)难成这个样子,需要挖掘三个数学性质,还说是简单,能不能不欺负人啊!

一、题目描述

0、前置知识

设\(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\)


证明:


组合数学知识,每个质数因子可以选择\(0 \sim r_k\)个,即\(1+r_k\)个,根据乘法原理,总的个数就是上式,证毕。


  • 约数和公式
    \(\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\)


证明:


和上面证明个数一样的思路,每个质数因子可以选择\(0\sim c_k\)个,以上面\(180\)为例,第一项质数因子\(2\),可以不要,也可以要\(1\)或\(2\)个,每种选择情况,因为还要和后面的式子进行乘积,不要的话,理解为\(2^0\);\(1\)个的话理解为\(2^1\);\(2\)个的话理解为\(2^2\),每项都按这个规则拆开,就是上面的公式了,证毕。


1、反素数定义

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

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

说白了,素数之所以称为素数,就是因为约数少,只有\(1\)和自己。

反素数,也就可以理解为:约数多,而且每个数的约数个数,都比小于自己的数字约数个数多。

2、求解

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

二、挖掘反素数的性质

性质1

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

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

性质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\),交换后该数变大了


性质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;
typedef long long LL;
int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23}; //前9位质数数组
int n; //讨论n以内的最大反素数

int MaxNumber; //在n以内,最大的反素数(约数个数最多)
int MaxCnt; //取到的最大反素数MaxNumber,它有多少个约数

/**
* @param step 枚举到第几个质数
* @param last 次数最大是多少,也可以理解为上一次的最次数,因为每次的幂次不能大于上一次的幂次
* @param number 当前路线已经取得的最大反素数
* @param cnt 当前路线已经取得的最大反素数,它有多少个约数
*/
void dfs(int step, int last, int number, int cnt) {
//(1)当前约数个数是大于最大约数个数
//(2)等于最大约数个数并且 当前取得的最大反素数 小于 最大反素数
if (cnt > MaxCnt || cnt == MaxCnt && number < MaxNumber)
MaxCnt = cnt, MaxNumber = number; //更新

if (step == 9) return; //边界

for (int i = 1; i <= last; i++) { //枚举每一个可行的幂次值(需要幂次小于等于上一次的幂次)
if ((LL)number * primes[step] > n) break; //超过了肯定不行
number *= primes[step]; //多乘一个质因子primes[step]
//注意这里需要在单独的一行写 number*=primes[step],因为这其实是一个累乘的原因,不能简单的去掉直接将乘法算式写入dfs函数中!
dfs(step + 1, i, number, cnt * (i + 1)); //约数个数公式
}
}

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

四、一点疑问

\(Q\):为什么在\(dfs\)中循环是从小到大的,而不是从大到小的,从大到小不行吗?

\(A\):也可以,但复杂的多,不推荐:

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long LL;
int ps[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
int n;

int MaxNumber; //在n以内,最大的反素数(约数个数最多)
int MaxCnt; //取到的最大反素数MaxNumber,它有多少个约数

//快速幂模板
LL qmi(LL a, LL b) {
LL res = 1;
while (b) {
if (b & 1) res *= a;
a *= a;
b >>= 1;
}
return res;
}

void dfs(int step, int last, LL number, int cnt) {
if (step == 9) return;
if (cnt > MaxCnt || (number < MaxNumber && cnt == MaxCnt))
MaxNumber = number, MaxCnt = cnt;
//如果真的要逆天而行,从大到小,那么首先需要计算ps[step]的last次方,这玩意不用快速幂就不好算
//而且可能很大,会爆long long,需要使用ULL
number *= qmi(ps[step], last);

for (int i = last; i >= 1; i--) { //如你所愿意,倒着从大到小
if (number <= n) dfs(step + 1, i, number, cnt * (i + 1)); //没法剪枝,只能判断不派发新任务
number /= ps[step]; //由乘变除
}
}

int main() {
scanf("%d", &n);
dfs(0, 30, 1, 1);
printf("%d\n", MaxNumber);
return 0;
}
  • 由小到大:

    (1)简单乘积即可,不用初始化一个极大的可能爆long long 的幂次结果.

    (2)方便剪枝,不行了就不再计算。

  • 由大到小:

    (1)需要初始化一个极大的幂次结果,不管有用没用,得先算出来,等着挨除。

    (2)无法剪枝,剪枝了后面就没法算了,只能是判断不新派发任务。

五、经验总结

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

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23}; //前9位质数数组
int n; //讨论n以内的最大反素数

int MaxNumber; //在n以内,最大的反素数(约数个数最多)
int MaxCnt; //取到的最大反素数MaxNumber,它有多少个约数

/**
* @param step 枚举到第几个质数
* @param last 次数最大是多少,也可以理解为上一次的最次数,因为每次的幂次不能大于上一次的幂次
* @param number 当前路线已经取得的最大反素数
* @param cnt 当前路线已经取得的最大反素数,它有多少个约数
*/
void dfs(int step, int last, int number, int cnt) {
if (step == 9) return; //边界
//(1)当前约数个数是大于最大约数个数
//(2)等于最大约数个数并且 当前取得的最大反素数 小于 最大反素数
if (cnt > MaxCnt || cnt == MaxCnt && number < MaxNumber)
MaxCnt = cnt, MaxNumber = number; //更新


for (int i = 1; i <= last; i++) { //枚举每一个可行的幂次值(需要幂次小于等于上一次的幂次)
if ((LL)number * primes[step] > n) break; //超过了肯定不行
number *= primes[step]; //多乘一个质因子primes[step]
//注意这里需要在单独的一行写 number*=primes[step],因为这其实是一个累乘的原因,不能简单的去掉直接将乘法算式写入dfs函数中!
dfs(step + 1, i, number, cnt * (i + 1)); //约数个数公式
}
}

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

输入\(2e9\),输出\(1536\)。

输入​​INT_MAX​​,即\(2147483647\),输出\(1600\)。




举报

相关推荐

0 条评论