题目描述:
求500万以内的所有亲和数,如果两个数a和b,a的所有真因数之和等于b,b的所有真因数之和等于a,则称a,b是一对亲和数。
真因数:除了本身以外的所有因数,
列如:220的真因数:1,2,4,5,10,11,20,22,44,55,110;
284的真因数:1,2,4,71,142.
220= 1+2+4+71+142 = sum[284]
284 = 1+2+4+5+10+11+20+22+44+55+110 = sum[220]
分析:
一个数的因数不超过它的一半,我们可以利用数组,把数作为数组下标,数组对应元素为真因数之和,然后遍历满足sum[sum[i]] = i的i,和sum[i]为一对亲和数。
首先初始化时,数组每一项都有一个1,因为1是所有数的真因数。从然后因数i从2开始到n/2,由于本身不是真因数,所以对应项从2i开始,3i,4i,5i….直到k*i>n,这些项都包含因数i。所有这些项sum[2i],sum[3i]….都要加上i。然后就可以开始遍历寻找满足亲和数条件的项。
i= 2时,sum[4]+=2,sum[6]+= 2;sum[8]+=2….
i = 3时,sum[6]+= 3;sum[9]+= 3;sum[12]+=3….
i = 4时,sum[8]+=4;sum[12] +=4…
/*************************************************************************
> File Name: find_qinhe.c
> Author: zxl
> mail: 857317335@qq.com
> Created Time: 2016年04月18日 星期一 20时36分58秒
************************************************************************/
#include<stdio.h>
#define SIZE 5000000
int main()
{
static int sum[5000001]; //大数组声明为静态,放在静态存储区
int i,j;
for(i = 1;i<= SIZE;i++)
sum[i] = 1; //每一项都含有1
for(i = 2;i+i<=SIZE;i++) //遍历所有因数,
{
j = 2*i;
while(j<=SIZE) //对于因数2,循环n/2次,对于因数3,循环n/3次,总的次数为n(1/2 + 1/3+ 1/4 + 1/n/2),总的时间复杂度为O(N* log N + N)
{
sum[j] +=i; //包含因数的项要加上因数
j+=i;
}
}
for(i = 220;i<=SIZE;i++)
{
if(sum[i]>i && sum[i] <=SIZE&& sum[sum[i]] == i)//使用sum[i] >i可以去掉重复的,比如sum[248] = 220
{
printf("%d,%d\n",i,sum[i]);
}
}
return 0;
}
需要注意的时,大数组定义的地方,如果在函数内,分配空间都在堆栈上,堆栈是比较有限的,大的空间都是分配到静态存储区,可以放在全局或者在函数里面定义加上static。
顺便扯点别的,可编程内存分为:静态存储区,堆区和栈区
静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,它主要存放静态数据、全局数据和常量。
栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率高,但是分配的内存 容量有限。
堆区:称动态内存分配,程序在运行的时候用malloc或new申请任意大小的内存,可以自己负责在适当的时候free或delete释放内存。动态内存的生存期可以由我们决定。
总结:连续数据的映射可以通过数组结构本身的特点替代,用来节约空间,这是数据结构的艺术,在大规模连续数据的回溯法处理上,通过转化为递推生成的方法,逆向思维操作,这是算法的艺术。