聪明的燕姿

聪明的燕姿

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

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

燕姿发现了一个神奇的算法:假设自己的号码牌上写着数字 $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

 

解题思路

  这里是该题的新题解,点击链接进行跳转:https://www.cnblogs.com/onlyblues/p/17206228.html

  昨天被这道题卡了一天,很多的地方都理解不了。

  另外,题目描述的明明是《遇见》好吧。建议改为“懒 惰 的 燕 姿”,不出专辑就算了,还要我帮她做数论题,关键是我还不会做。[doge]

  题目就是问有多少个数的约数和恰好等于$s$。

  根据唯一分解定理,一个数可以唯一分解成若干个素数的乘积形式,$N = P_{1}^{\alpha_{1}} \cdot P_{2}^{\alpha_{2}} \cdot \cdots \cdot P_{n}^{\alpha_{n}}$,其中$P_{i}$为素数,且$\alpha_{i} > 0, P_{1} < P_{2} < \cdots < P_{n}$。

  首先一个数所有约数的个数公式:$\left( {\alpha_{1} + 1} \right) \cdot \left( {\alpha_{2} + 1} \right) \cdot \cdots \cdot \left( {\alpha_{n} + 1} \right)$。

  一个数所有约数的和公式:$\left( {1 + P_{1} + P_{1}^{2} + \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)$。

  首先可以发现我们要找的数一定是小于$s$的,如果有$x \geq s$,因为$1$和$x$都是$x$的约数,因此仅看这两个约数的和$x + 1$已经大于$s$了,因此要找的数一定小于$s$。

  因此我们要的数的约数和应该满足这种形式:$$s = \left( {1 + P_{1} + P_{1}^{2} + \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 + P_{i} + \cdots + P_{i}^{\alpha_{i}}} \right)$,粗略估计一下一共有多少项$\left( {1 + P_{i} + \cdots + P_{i}^{\alpha_{i}}} \right)$。

$$\begin{align*} 2 \times {10}^{9} &= \left( {1 + P_{1} + P_{1}^{2} + \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) \end{align*} $$

  可以发现通过粗略的估计,当枚举到素数$23$时还是小于$2 \times {10}^{9}$的,当枚举到素数$29$时已经大于$2 \times {10}^{9}$了,因此可以发现$\left( {1 + P_{i} + \cdots + P_{i}^{\alpha_{i}}} \right)$一共不会超过$10$项,所以可以用dfs来爆搜每一个$P_{i}$和$\alpha_{i}$,递归的深度不会超过$10$。

  另外一个问题是,如果我们在每一层枚举$P_{i}$时直接从$2$枚举到$s$,时间复杂度就很高了,因此需要剪支。

  可以证明对于等式$s = \left( {1 + P_{1} + P_{1}^{2} + \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)$,如果有$P_{1} \geq \sqrt{s}$,那么一定只会是$s = 1 + P_{1}$这个形式。补充:因为对于每一层的$s$,其实我们都是在搜$P_{1}$,然后用$s$除以这一项,得到$\frac{s}{{1 + P_{1} + \cdots + P_{1}^{\alpha_{1}}}}$,下一层就是搜索这个值。

  分情况讨论:

  如果$s$分解质因数后是这种形式:$s = {1 + P_{1} + \cdots + P_{1}^{\alpha_{1}}}$,即只有一种质因子$P_{1}$。反证法,假设$P_{1}$的次幂至少$\geq 2$,又因为$P_{1} \geq \sqrt{s}$,因此光是前几项的和就有$1 + P_{1} + P_{1}^{2} > s$,矛盾,因此必然是有$s = 1 + P_{1}$。

  如果如果$s$分解质因数后是这种形式:$s = \left( {1 + P_{1}} \right) \cdot \left( {1 + P_{2}} \right) \cdot \cdots \cdot \left( {1 + P_{n}} \right)$(上面证明每一项的次幂必然小于$2$),又因为我们规定$P_{1} < P_{2} < \cdots < P_{n}$,同时有$P_{1} \geq \sqrt{s}$,因此光看前两项的乘积就有$\left( {1 + P_{1}} \right) \cdot \left( {1 + P_{2}} \right) = 1 + P_{1} + P_{2} + P_{1} \times P_{2} > P_{1} \times P_{2} > \sqrt{s} \times \sqrt{s} = s$,矛盾,因此必然是有$s = 1 + P_{1}$。

  再证明唯一性。假设存在两个$P_{i} \geq \sqrt{s}, P_{j} \geq \sqrt{s}$,且$P_{i} < P_{j}$,根据上面的证明,一定会有$s = 1 + P_{i}, s = 1 + P_{j}$。因为$s = s$,所以有$1 + P_{i} = 1 + P_{j}$,即$P_{i} = P_{j}$,这就与$P_{i} < P_{j}$矛盾了,因此唯一性得到证明,即如果存在一个$P_{1} \geq \sqrt{s}$且满足$s = 1 + P_{1}$,那么有且仅有$P_{1}$这一个。

  因此结论是,如果某个数$s$质因数分解后,第一项中的质数$P_{1} \geq \sqrt{s}$,那么$s$的形式一定会是$s = 1+ P_{1}$,且$\geq \sqrt{s}$的$P_{1}$有且只有这一个。

  所以我们在每一层搜索第一项$\left( {1 + P_{1} + \cdots + P_{1}^{\alpha_{1}}} \right)$的时候,因为$\geq \sqrt{s}$的$P_{1}$最多只会有一个,且满足$s = 1 + P_{1}$这个形式,所以对于这种情况只需要进行特判一下是否满足该形式就可以了。即便是存在$P_{1} \geq \sqrt{s}$满足上一种情况,还是有可能存在$P_{1} < \sqrt{s}$,该质数的若干个次幂和即${1 + P_{1} + \cdots + P_{1}^{\alpha_{1}}}$可以整除$s$,因此剩下的遍历范围不超过$\sqrt{s}$就可以了,看看是否存在有$P_{1} < \sqrt{s}$的项,满足整除$s$的情况。

  另外一些细节会在注释中补充,AC代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int N = 1e5 + 10;
 6 
 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 n) {
22     if (n < N) return !vis[n];
23     for (int i = 0; primes[i] <= n / primes[i]; i++) {
24         if (n % primes[i] == 0) return false;
25     }
26     
27     return true;
28 }
29 
30 // cur表示当前要从哪个质数的下标开始枚举,prod表示要找的答案的部分质数乘积,s表示当前要分解约数和
31 void dfs(int cur, int prod, int s) {
32     if (s == 1) {   // s == 1表示约数和已经分解完了,每一个Pi和αi已经找到
33         ans[sz++] = prod;   // 答案就是各项Pi^αi的乘积
34         return;
35     }
36     
37     // 先特判一下是否存在一个Pi >= sqrt(s),满足s == 1 + Pi -> Pi == s - 1
38     // 如果满足,等价于Pi应该大于等于当前枚举的质数,即s - 1 >= primes[cur],且Pi要是一个质数
39     // 不return是因为Pi >= sqrt(s)只是我们枚举的一种情况而已,接下来还要枚举Pi < sqrt(s)的情况
40     if (s - 1 >= primes[cur] && is_prime(s - 1)) ans[sz++] = prod * (s - 1);
41     
42     // 特判后,Pi的枚举范围就缩小到Pi^2 < s
43     for (int i = cur; primes[i] < s / primes[i]; i++) {
44         int t = primes[i];
45         // j == 1 + Pi^1 + Pi^2 + ... + Pi^αi
46         for (int j = 1 + primes[i]; j <= s; t *= primes[i], j += t) {
47             if (s % j == 0) dfs(i + 1, prod * t, s / j);    // 只有j是s的某一项,即j可以整除s,即s % j == 0才可以继续往下搜
48         }
49     }
50 }
51 
52 int main() {
53     get_prime(N - 1);   // 筛质数,我们用到的质数小于sqrt(2e9)
54     
55     int n;
56     while (~scanf("%d", &n)) {
57         sz = 0;
58         dfs(0, 1, n);
59         
60         printf("%d\n", sz);
61         if (sz) {
62             sort(ans, ans + sz);
63             for (int i = 0; i < sz; i++) {
64                 printf("%d ", ans[i]);
65             }
66             printf("\n");
67         }
68     }
69     
70     return 0;
71 }

 

参考资料

  AcWing 1296. 聪明的燕姿(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/746/

posted @ 2022-03-05 09:09  onlyblues  阅读(192)  评论(0编辑  收藏  举报
Web Analytics