聪明的燕姿

聪明的燕姿

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

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

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

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

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

输入格式

输入包含 k 组数据。

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

输出格式

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

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

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

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

数据范围

1k100,
1S2×109

输入样例:

42

输出样例:

3
20 26 41

 

解题思路

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

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

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

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

i=1nj=0αiPij=(P10+P11++P1α1)(P20+P21++P2α2)(Pn0+Pn1++Pnαn)

  因此问题就变成了有多少个质因子 Pi 以及对应的次幂 αi 所构成的项 j=0αiPij 累乘得到的结果恰好为 S

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

2×109=(1+P1++P1α1)(1+P2++P2α2)(1+Pn++Pnαn)>(1+2)(1+3)(1+5)(1+7)(1+11)(1+13)(1+17)(1+19)(1+23)=836075520

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

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

  假设在约数之和的公式中存在某个质因子满足 PiS,那么该质因子对应的次幂 αi 一定恰好为 1,即对应的项一定是 1+Pi1 这个形式,而不可能是 1+Pi1+Pi2+ 这个形式。因为即使考虑 1+Pi1+Pi2 就已经有 1+Pi1+Pi21+S+S>S,此时约数之和就必定超过 S 了。

  同时还可以发现如果存在某个质因子满足 PiS,那么该质因子就只能恰好是 Pn 这个最大的质因子。反证法,否则至少存在两个质因子满足 Pj>PiS,根据上面的结论必然有 αi=αj==αn=1,此时的约数之和为

i=1nj=0αiPij=(P10+P11++P1α1)(P20+P21++P2α2)(Pi0+Pi1)(Pj0+Pj1)(Pn0+Pn1)>(Pi0+Pi1)(Pj0+Pj1)>1+Pi+Pj+PiPj>PiPjS

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

  综合上面的结论,我们得到在约数之和的公式中,最多只会存在一个质因子 PnS (即最大的那个质因子),且对应的次幂一定是 αn=1

  当 Pn<S,那么 S=i=1nj=0αiPij

  当 PnS,有以下两种情况:

  1. S=i=1nj=0αiPij ,  n>1
  2. S=1+P1 ,  n=1

  因此当我们枚举质因子 Pi 时只需要枚举到 S 就可以了,超过 S 的质因子 Pn 直接进行特判。假设之前枚举的 n1 个质数得到的累乘结果记为 i=1n1j=0αiPij=prod,由于 Pn 必然是公式中最大的那个质因子,因此如果要满足 prod(1+Pn)=S,那么一定有 Pn=Sprod1,同时 Pn 是质数且 Pn>Pn1, PnS

  先给出这种搜索顺序的代码(其实这个搜索代码还是会 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 }
复制代码

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

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

  否则我们先判断是否存在质因子 PnS 使得 prod(r1)=S,在代码中 Pn=Sprod1=r1。如果满足 r1 是质数,且 r1primes[cur], r1S,那么 prod(r1) 就是我们要找的答案。

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

  可是这样做还是会超时的,因为计算量大概是 (π(S)logS)9 级别。继续对枚举质数的部分进行优化,可以发现在每一层中我们做的事情都是从第 cur 个质数开始枚举,使得累乘的结果不超过 r。类比上面的分析,我们知道枚举的质数肯定要小于 r,并且一样可以枚举不超过 r 的质数,然后超过 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) ,这是因为 r1 一定大于 r。这个搜索代码就可以过了。

  至此搜索的部分就已经讲完了,剩下的就只需要筛出不超过 2×109 的所有质数,以及写个判断是否为质数的函数就可以了。

  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 @   onlyblues  阅读(100)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示