聪明的燕姿

聪明的燕姿

城市中人们总是拿着号码牌,不停寻找,不断匹配,可是谁也不知道自己等的那个人是谁。

可是燕姿不一样,燕姿知道自己等的人是谁,因为燕姿数学学得好!

燕姿发现了一个神奇的算法:假设自己的号码牌上写着数字 $S$,那么自己等的人手上的号码牌数字的所有正约数之和必定等于 $S$。

所以燕姿总是拿着号码牌在地铁和人海找数字(喂!这样真的靠谱吗)。

可是她忙着唱《绿光》,想拜托你写一个程序能够快速地找到所有自己等的人。

输入格式

输入包含 $k$ 组数据。

对于每组数据,输入包含一个号码牌 $S$。

输出格式

对于每组数据,输出有两行。

第一行包含一个整数 $m$,表示有 $m$ 个等的人。

第二行包含相应的 $m$ 个数,表示所有等的人的号码牌。

注意:你输出的号码牌必须按照升序排列。

数据范围

$1 \leq k \leq 100$,
$1 \leq S \leq 2 \times {10}^{9}$

输入样例:

42

输出样例:

3
20 26 41

 

解题思路

  最近又重新做了下这道题,顺便看了下以前写的题解发现很多东西写得莫名奇妙的()

  因此打算重新写过一篇,以前的题解就不删了,可以点击这里的链接查看:聪明的燕姿

  写完后发现还是很长,看不下去可以关掉,毕竟不敢对题解的质量进行保证qwq。

  首先看到约数之和就很容易想到公式

$$\prod\limits_{i=1}^{n}{\sum\limits_{j=0}^{\alpha_i}{P_i^j}} = \left( {P_1^0 + P_1^1 + \cdots + P_1^{\alpha_1}} \right) \cdot \left( {P_2^0 + P_2^1 + \cdots + P_2^{\alpha_2}} \right) \cdots \left( {P_n^0 + P_n^1 + \cdots + P_n^{\alpha_n}} \right)$$

  因此问题就变成了有多少个质因子 $P_i$ 以及对应的次幂 $\alpha_i$ 所构成的项 $\sum\limits_{j=0}^{\alpha_i}{P_i^j}$ 累乘得到的结果恰好为 $S$。

  看上去似乎没有很好的解决方法,那只能通过枚举所有的 $P_i$ 和 $\alpha_i$ 来统计了。我们来分析一下暴搜的可行性,首先最多会有多少项进行累乘呢?通过放缩容易发现

$$\begin{align*} 2 \times {10}^{9} &= \left( {1 + P_{1} + \cdots + P_{1}^{\alpha_{1}}} \right) \cdot \left( {1 + P_{2} + \cdots + P_{2}^{\alpha_{2}}} \right) \cdot \cdots \cdot \left( {1 + P_{n} + \cdots + P_{n}^{\alpha_{n}}} \right) \\ &> \left( {1 + 2} \right) \cdot \left( {1 + 3} \right) \cdot \left( {1 + 5} \right) \cdot \left( {1 + 7} \right) \cdot \left( {1 + 11} \right) \cdot \left( {1 + 13} \right) \cdot \left( {1 + 17} \right) \cdot \left( {1 + 19} \right) \cdot \left( {1 + 23} \right) \\ &= 836075520 \end{align*}$$

  因此递归的深度不会超过 $10$ 层。所以在每一层中,我们可以从小到大来枚举质数 $P_i$,同时对于每个 $P_i$ 也从小到大枚举对应的次幂 $\alpha_i$。为了防止重复搜索,当递归到下一场时我们规定必须从大于 $P_i$ 的质数开始枚举,这样就能保证在得到的约数和中,有 $P_1 < P_2 < \cdots < P_n$。

  那么质数 $P_i$ 最大可以枚举到多少呢?首先很容易知道 $P_i$ 一定小于 $S$,因为 $1$ 和 $S$ 本身就是 $S$ 的两个约数,而 $1+S$ 就超过 $S$ 了,因此一定有 $P_i < S$。所以按照上面的做法的,每一层枚举的质数都小于 $S$,由于最多要递归 $9$ 层来枚举,因此计算量就达到 $\left( \pi \left( S \right) \cdot \log {S} \right)^9$ 的级别(其中 $\pi(x)$ 表示不超过 $x$ 的质数的个数)。我们继续看一下还有什么性质。

  假设在约数之和的公式中存在某个质因子满足 $P_i \geq \sqrt{S}$,那么该质因子对应的次幂 $\alpha_i$ 一定恰好为 $1$,即对应的项一定是 $1 + P_i^1$ 这个形式,而不可能是 $1 + P_i^1 + P_i^2 + \cdots$ 这个形式。因为即使考虑 $1 + P_i^1 + P_i^2$ 就已经有 $1 + P_i^1 + P_i^2 \geq 1 + \sqrt{S} + S > S$,此时约数之和就必定超过 $S$ 了。

  同时还可以发现如果存在某个质因子满足 $P_i \geq \sqrt{S}$,那么该质因子就只能恰好是 $P_n$ 这个最大的质因子。反证法,否则至少存在两个质因子满足 $P_j > P_i \geq \sqrt{S}$,根据上面的结论必然有 $\alpha_i = \alpha_j = \cdots = \alpha_n = 1$,此时的约数之和为

$$\begin{align*}
\prod\limits_{i=1}^{n}{\sum\limits_{j=0}^{\alpha_i}{P_i^j}} &= \left( {P_1^0 + P_1^1 + \cdots + P_1^{\alpha_1}} \right) \cdot \left( {P_2^0 + P_2^1 + \cdots + P_2^{\alpha_2}} \right) \cdots \left( {P_i^0 + P_i^1} \right) \cdot \left( {P_j^0 + P_j^1} \right) \cdots \left( {P_n^0 + P_n^1} \right) \\ &> \left( {P_i^0 + P_i^1} \right) \cdot \left( {P_j^0 + P_j^1} \right) \\ &> 1 + P_i + P_j + P_i \cdot P_j \\ &> P_i \cdot P_j \\ &\geq S
\end{align*}$$

  这就与约数之和为 $S$ 矛盾了。

  综合上面的结论,我们得到在约数之和的公式中,最多只会存在一个质因子 $ P_n \geq\sqrt{S}$ (即最大的那个质因子),且对应的次幂一定是 $\alpha_n = 1$。

  当 $P_n < \sqrt{S}$,那么 $S = \prod\limits_{i=1}^{n}{\sum\limits_{j=0}^{\alpha_i}{P_i^j}}$。

  当 $P_n \geq \sqrt{S}$,有以下两种情况:

  1. $S = \prod\limits_{i=1}^{n}{\sum\limits_{j=0}^{\alpha_i}{P_i^j}} \ , \ \ n > 1$
  2. $S = 1 + P_1 \ , \ \ n = 1$

  因此当我们枚举质因子 $P_i$ 时只需要枚举到 $\sqrt{S}$ 就可以了,超过 $\sqrt{S}$ 的质因子 $P_n$ 直接进行特判。假设之前枚举的 $n-1$ 个质数得到的累乘结果记为 $\prod\limits_{i=1}^{n-1}{\sum\limits_{j=0}^{\alpha_i}{P_i^j}} = \text{prod}$,由于 $P_n$ 必然是公式中最大的那个质因子,因此如果要满足 $\text{prod} \cdot (1 + P_n) = S$,那么一定有 $P_n = \dfrac{S}{\text{prod}} -1$,同时 $P_n$ 是质数且 $P_n > P_{n-1}, \ P_n \geq \sqrt{S}$。

  先给出这种搜索顺序的代码(其实这个搜索代码还是会 TLE):

 1 void dfs(int cur, int prod, int num) {
 2     if (prod == s) {
 3         ans[sz++] = num;
 4         return;
 5     }
 6     int r = s / prod;
 7     if (is_prime(r - 1) && r - 1 >= primes[cur] && (r - 1) >= s / (r - 1)) ans[sz++] = num * (r - 1);
 8     for (int i = cur; primes[i] <= s / primes[i]; i++) {
 9         for (int j = 1 + primes[i], k = primes[i]; j <= s; k *= primes[i], j += k) {
10             if (r % j == 0) dfs(i + 1, prod * j, num * k);
11         }
12     }
13 }

  先解释以下各个参数的含义,首先 $\text{cur}$ 表示当前从第 $\text{cur}$ 个质数开始枚举。$\text{prod}$ 的定义与上面相同,即之前枚举质数的各项累乘。$\text{num}$ 表示约数之和为 $\text{prod}$ 所对应的那个数,即 $\text{num}$ 的约数之和等于 $\text{prod}$。

  很明显当 $\text{prod} = S$,那么此时 $\text{num}$ 就是我们要找的数。

  否则我们先判断是否存在质因子 $P_n \geq \sqrt{S}$ 使得 $\text{prod} \cdot (r - 1) = S$,在代码中 $P_n = \dfrac{S}{\text{prod}} -1 = r-1$。如果满足 $r-1$ 是质数,且 $r-1 \geq \text{primes}[\text{cur}], \ r - 1 \geq \sqrt{S}$,那么 $\text{prod} \cdot (r - 1)$ 就是我们要找的答案。

  然后再去枚举考虑不超过 $\sqrt{S}$ 的质因子,以及对应的次幂,往下一层递归。

  可是这样做还是会超时的,因为计算量大概是 $\left( \pi \left( \sqrt{S} \right) \cdot \log {S} \right)^9$ 级别。继续对枚举质数的部分进行优化,可以发现在每一层中我们做的事情都是从第 $\text{cur}$ 个质数开始枚举,使得累乘的结果不超过 $r$。类比上面的分析,我们知道枚举的质数肯定要小于 $r$,并且一样可以枚举不超过 $\sqrt{r}$ 的质数,然后超过 $\sqrt{r}$ 的质因子进行特判就可以了,代码就变成下面的样子:

 1 void dfs(int cur, int prod, int num) {
 2     if (prod == s) {
 3         ans[sz++] = num;
 4         return;
 5     }
 6     int r = s / prod;
 7     if (is_prime(r - 1) && (r - 1) >= primes[cur]) ans[sz++] = num * (r - 1);
 8     for (int i = cur; primes[i] <= r / primes[i]; i++) {
 9         for (int j = 1 + primes[i], k = primes[i]; j <= r; k *= primes[i], j += k) {
10             if (r % j == 0) dfs(i + 1, prod * j, num * k);
11         }
12     }
13 }

  可以发现少了判断条件 (r - 1) >= r / (r - 1) ,这是因为 $r-1$ 一定大于 $\sqrt{r}$。这个搜索代码就可以过了。

  至此搜索的部分就已经讲完了,剩下的就只需要筛出不超过 $\sqrt{2 \times 10^9}$ 的所有质数,以及写个判断是否为质数的函数就可以了。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 45000;
 5 
 6 int s;
 7 int primes[N], cnt;
 8 bool vis[N];
 9 int ans[N], sz;
10 
11 void get_prime(int n) {
12     for (int i = 2; i <= n; i++) {
13         if (!vis[i]) primes[cnt++] = i;
14         for (int j = 0; primes[j] <= n / i; j++) {
15             vis[primes[j] * i] = true;
16             if (i % primes[j] == 0) break;
17         }
18     }
19 }
20 
21 bool is_prime(int x) {
22     if (x < N) return !vis[x];
23     for (int i = 0; primes[i] <= x / primes[i]; i++) {
24         if (x % primes[i] == 0) return false;
25     }
26     return true;
27 }
28 
29 void dfs(int cur, int prod, int num) {
30     if (prod == s) {    // 搜索得到的质数和prod等于s
31         ans[sz++] = num;
32         return;
33     }
34     int r = s / prod;   // 要凑的剩余部分
35     if (is_prime(r - 1) && (r - 1) >= primes[cur]) ans[sz++] = num * (r - 1);   // 先考虑质因子大于sqrt(r)的情况
36     for (int i = cur; primes[i] <= r / primes[i]; i++) {    // 再考虑质因子小于sqrt(r)的情况
37         for (int j = 1 + primes[i], k = primes[i]; j <= r; k *= primes[i], j += k) {    // 枚举质数i对应的次幂
38             if (r % j == 0) dfs(i + 1, prod * j, num * k);
39         }
40     }
41 }
42 
43 int main() {
44     get_prime(N - 1);   // 筛出不超过sqrt(2e9)的质数
45     while (~scanf("%d", &s)) {
46         sz = 0;
47         dfs(0, 1, 1);   // 从第0个质数开始枚举搜索
48         printf("%d\n", sz);
49         if (sz) {
50             sort(ans, ans + sz);
51             for (int i = 0; i < sz; i++) {
52                 printf("%d ", ans[i]);
53             }
54             printf("\n");
55         }
56     }
57     
58     return 0;
59 }

 

参考资料

  聪明的燕姿:https://www.cnblogs.com/onlyblues/p/15966022.html

posted @ 2023-03-11 15:54  onlyblues  阅读(82)  评论(0编辑  收藏  举报
Web Analytics