P1463 [POI 2001 R1 / ZJOI2006 / HAOI2007] 反素数
题目描述
对于任何正整数 \(x\),其约数的个数记作 \(g(x)\)。例如 \(g(1)=1\),\(g(6)=4\)。
如果某个正整数 \(x\) 满足:\(\forall 0 \lt i \lt x\),都有 \(g(x) \gt g(i)\),则称 \(x\) 为反素数。例如,\(1,2,4,6,12,24\) 等都是反素数。
现在给定一个正整数 \(N\),你能求出不超过 \(N\) 的最大的反素数么?
线性筛求约数个数 + 前缀最大值
- 利用线性筛求解得到\(1\sim n\)中所有自然数的约数个数,
preMax记录d的前缀最大值,从后往前遍历,由“反素数”条件d[i] > preMax[i]得到不超过\(N\)的最大的反素数并进行输出。
#include <iostream>
#include <vector>
int main() {
int n;
std::cin >> n;
std::vector<int> primes;
std::vector<bool> isPrime(n + 1, true);
std::vector<int> d(n + 1, 0);
std::vector<int> num(n + 1, 0);
isPrime[0] = isPrime[1] = false;
d[1] = 1;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes.push_back(i);
num[i] = 1;
d[i] = 2;
}
for (int pj : primes) {
if (i * pj > n) break;
isPrime[i * pj] = false;
if (i % pj == 0) {
num[i * pj] = num[i] + 1;
d[i * pj] = d[i] / (num[i] + 1) * (num[i * pj] + 1);
break;
} else {
num[i * pj] = 1;
d[i * pj] = d[i] * d[pj];
}
}
}
// 前缀最大值
std::vector<int> preMax(n + 1, 0);
for (int i = 1; i <= n; i++) {
preMax[i] = std::max(d[i - 1], preMax[i - 1]);
}
for (int i = n; i >= 1; i--) {
if (d[i] > preMax[i]) {
std::cout << i << std::endl;
break;
}
}
return 0;
}
- 时间复杂度:\(O(N)\)
- 空间复杂度:\(O(N)\)
- 代码并未过完所有样例,内存远远超出题目要求的\(125MB\),即使内存满足要求了,该算法在\(2\times 10^9\)的数据规模下,时间也达不到要求。
DFS
由于反素数的约数个数是严格递增的,越往后的反素数,约数越多。由算数基本定理,假设\(N\)的唯一分解式为:
\[x=p_1^{k_1}\times p_2^{k_2}\times \cdots\times p_m^{k_m}
\]
它的约数个数为:
\[g(x)=(k_1+1)\times(k_2+1)\times\cdots\times(k_m+1)
\]
由于我们要寻找反素数,它必须满足在约数尽可能多的情况下,数值尽可能小。
例如\(\{x_1,x_2,\cdots,x_s\}\)中\(x_1<x_2<\cdots<x_s\)且\(g(x_1)=g(x_2)=\cdots=g(x_s)\),我们挑选出来的数字一定是\(g(x_1)\),否则就不满足“反素数”的定义了。
因此它必须满足以下两个性质:
- 质因子必须是从小到大的连续的质数。如果不连续(比如包含\(2\)和\(5\)但不包含\(3\)),我们把\(5\)换成\(3\),数值变小了,但约数个数不变。
- 质因子的质数必须单调不降,即\(k_1\ge k_2\ge\cdots\ge k_m\).因为如果后面的指数比前面的大(比如\(2^1\times 3^2\)),把它们调换(变成\(2^2\times 3^1\)),约数个数不变,但是数值减小了。
有了这两个性质,就会发现满足条件的数非常少。并且,前十几个连续素数乘积就已经超过了数据的上限\(2\times 10^9\):
\[2\times 3\times 5\times 7\times 11\times 13\times 17\times 19\times 23\times 29\times 31>2\times 10^9
\]
因此,我们只需要用\(DFS\),尝试给前十几个小的指数分配指数,就能够找到答案:
#include <iostream>
int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
long long N;
long long maxDivisors = 0; // 记录搜索过程中最大的约数个数
long long bestAns = 1; // 记录符合条件的最大素数
/**
* DFS 搜索
* @param pIdx 当前枚举到第几个质数
* @param currentNum 当前累乘的数值
* @param currentDivisors 当前数值拥有的约数个数
* @param limitExp 当前质数分配的指数上限
*/
void dfs(int pIdx, long long currentNum, int currentDivisors, int limitExp) {
// 如果找到了更多的约数,或者约束一样多但数值更小,则更新答案
if (currentDivisors > maxDivisors || (currentDivisors == maxDivisors && currentNum < bestAns)) {
maxDivisors = currentDivisors;
bestAns = currentNum;
}
// 尝试给当前指数分配指数 1,2,3,...,limitExp
long long temp = currentNum;
for (int i = 1; i <= limitExp; i++) {
// 如果再乘当前的质数就超过了N,直接退出循环
if (N / primes[pIdx] < temp) break;
temp *= primes[pIdx];
// 递归下一层,处理下一个素数
// 下一个素数的指数上限不能超过当前质数的指数
dfs(pIdx + 1, temp, currentDivisors * (i + 1), i);
}
}
int main() {
std::cin >> N;
dfs(0, 1, 1, 31);
std::cout << bestAns << std::endl;
return 0;
}

P1463 [POI 2001 R1 / ZJOI2006 / HAOI2007] 反素数
浙公网安备 33010602011771号