当前位置: 代码迷 >> 综合 >> acwing 1296.聪明的燕姿
  详细解决方案

acwing 1296.聪明的燕姿

热度:55   发布时间:2023-11-24 15:17:29.0

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

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

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

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

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

输入格式
输入包含 k 组数据。

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

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

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

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

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

数据范围
1≤k≤100,
1≤S≤2×109
输入样例:
42
输出样例
3
20 26 41


算术基本定理

N = P1^a1 * P2^a2 * … * Pn^an
则N的约数个数为(a1+1)(a2+1)…(an+1)
假设N的一个约数为D
D = P1^b1 * P2^b2 * … * Pn^bn
其中bi可以取到0,范围是0<= bi <= ai
因为只有和N的质因数一一对应一定能得到约数
因为bi可以从0取到ai,那么每一个bi就有(ai+1)种选法
约数的个数就是每个bi对应多少种选法相乘
即约数个数就为(a1+1)(a2+1)…(an+1)
约数之和S = (1+p1+p12+…+p1a1)(1+p2+p22+…+p2a2)…(1+pn+pn2+…+pnan)

这个怎么理解呢

因为每一个约数为D = P1^b1 * P2^b2 * … * Pn^bn
那么S = (1+p1+p12+…+p1a1)(1+p2+p22+…+p2a2)…(1+pn+pn2+…+pnan)

这个公式的意思就是从每个括号里面取出来一个数然后相乘,就能得到一个约数Di
然后所有的约数Di相加就得到约数之和S

举个例子
对于S = 42来说,42对应的结果里面有一个为20,20 = (1 + 2 + 4 + 5 + 10 + 20)
20 = 2^2*5
对于两个质因数2和5来说
2可以取0,1,2次,5可以取0,1次
所以S = (1+2+2^2)(1+5) = 42


dfs应该设置成三个参数dfs(last,product,S)

1.last参数
表示上一个枚举的质数是谁,我们这样枚举的目的就是先把前面符合条件的质数枚举完了再枚举后面的质数,这样不会带来重复,降低了时间复杂度

比如质数为P = 2,3,5,7…

如果枚举2之后再dfs到下一层,那么这个时候就应该再从3开始进行枚举而不是再从头开始枚举

2.product参数
product表示 S = (1+p1+p12+…+p1a1)(1+p2+p22+…+p2a2)…(1+pn+pn2+…+pnan)中

当前进行到哪一个括号里面的最高次项Pi^ai的乘积和
比如S = (1+2+22)(1+3+32+3^3)(1+…)
则dfs到第三层的时候product = 22*33 (product : 1 – > 2^2 – > 22*33)

又由算术基本定理可知
一个数N = P1^a1 * P2^a2 * … * Pn^an
product = P1^a1 * P2^a2*…

如果product要从第一层(一开始product初始化为1)进到第二层,此时product(2) = product(1) * P1^a1
S = S’ / (1+p1+p12+…+p1a1)

然后再dfs(last,product(2),S) ==(等价于) dfs(last , product(1)*P1^a1 , S’/(1+p1+p12+…+p1a1))

3.S参数
从上面的分析可知S参数就代表着从一开始的S除以(1+pk+pk2+…+pkak)后剩余的乘积

#include<iostream>
#include<algorithm>using namespace std;const int N = 50000;//sqrt(2e9)int primes[N], cnt = 0;
int s, res[N], len;
bool st[N];void get_primes(int n)
{
    for(int i = 2; i <= n; i ++){
    if(!st[i]) primes[cnt ++] = i;for(int j = 0; primes[j] * i <= n; j ++){
    st[primes[j] * i] = true;if(i % primes[j] == 0) break;}}
}int is_primes(int n)
{
    if(n < N) return !st[n];//没有被筛过说明就是质数,返回truefor(int i = 0; primes[i] <= n / primes[i]; i ++){
    if(n % primes[i] == 0) return false;} return true;
}void dfs(int last, int product, int s)//last表示上一个用的质数的下标是什么,product当前最高次项的结果,S表示每次处理后剩余多少
{
    if(s == 1){
    res[len ++] = product;return;}//比如20 = 2^2 * 5//N = P1^a1 * P2^a2 * ... * Pn^an//S = (1+p1+p1^2+...+p1^a1)(1+p2+p2^2+...+p2^a2)...(1+pn+pn^2+...+pn^an)//42 = (1 + 2 + 2^2)*(1 + 5),其中2^2和5就分别是最高次项p1^2*p2^1if(s  - 1 > ((last < 0) ? 0 : primes[last]) && is_primes(s - 1)){
    res[len ++] = product * (s - 1);}for(int i = last + 1; primes[i] <= s / primes[i]; i ++){
    int p = primes[i];for(int j = p + 1, t = p; j <= s; t *= p, j += t){
    if(s % j == 0){
    dfs(i, product * t, s / j);}}}
}int main()
{
    get_primes(N - 1);while(cin >> s){
    len = 0;dfs(-1, 1, s);sort(res, res + len);cout << len << endl;if(len){
    sort(res, res + len);for(int i = 0; i < len; i ++) cout << res[i] << ' ';cout << endl;}}return 0;
}

注意

1.求质数的过程应该用线性筛的方法去做
2.dfs(last,product,S)其中last应该初始化为-1
这样我们在一开始判断S-1大于前面的质数的时候应该特判,S-1 > ((last < 0) ? 0 : primes[last])
因为最小是(1+2)那么S起码是2,所以应该有S > 1 即S-1 > 0