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)\),否则就不满足“反素数”的定义了。
因此它必须满足以下两个性质:

  1. 质因子必须是从小到大的连续的质数。如果不连续(比如包含\(2\)和\(5\)但不包含\(3\)),我们把\(5\)换成\(3\),数值变小了,但约数个数不变。
  2. 质因子的质数必须单调不降,即\(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;
}
posted @ 2026-09-02 16:34  chen_xing  阅读(13)  评论(0)    收藏  举报