今天看了个题,感觉碉堡了,特此记录
所谓亲和数,就是指a和b,其中a的所有真因数的和是b,b的所有真因数的和是a
例如
220的真因子是:1、2、4、5、10、11、20、22、44、55、110
284的真因子是:1、2、4、71、142
而这两个数恰恰等于对方的真因子各自加起来的和(sum[i]表示数i 的各个真因子的和),即
220=1+2+4+71+142=sum[284],
284=1+2+4+5+10+11+20+22+44+55+110=sum[220]。
得284的真因子之和sum[284]=220,且220的真因子之和sum[220]=284,即有sum[220]=sum[sum[284]]=284。
OK,明白了亲和数,然后就一头雾水了。。。只会蛮力法的废柴。。。排除dp,二分等等,这里的关键就是要为每个数,都要计算出它的真因数的和
如果一直蛮力法去计算各个数的真因数的和,那效率就很低了
//求解亲和数问题
//第一个for和第二个for循环是logn(调和级数)*N次遍历,第三个for循环扫描O(N)。
//所以总的时间复杂度为 O(n*logn)+O(n)=O(N*logN)(其中logN为调和级数)。
//关于第一个for和第二个for寻找中,调和级数的说明:
//比如给2的倍数加2,那么应该是 n/2次,3的倍数加3 应该是 n/3次,...
//那么其实就是n*(1+1/2+1/3+1/4+...1/(n/2))=n*(调和级数)=n*logn。
//copyright@ 上善若水
//July、updated,2011.05.24。
#include<stdio.h>
int sum[5000010]; //为防越界
int main()
{
int i, j;
for (i = 0; i <= 5000000; i++)
sum[i] = 1; //1是所有数的真因数所以全部置1
for (i = 2; i + i <= 5000000; i++) //预处理,预处理是logN(调和级数)*N。
//@litaoye:调和级数1/2 + 1/3 + 1/4......的和近似为ln(n),
//因此O(n *(1/2 + 1/3 + 1/4......)) = O(n * ln(n)) = O(N*log(N))。
{
//5000000以下最大的真因数是不超过它的一半的
j = i + i; //因为真因数,所以不能算本身,所以从它的2倍开始
while (j <= 5000000)
{
//将所有i的倍数的位置上加i
sum[j] += i;
j += i;
}
}
for (i = 220; i <= 5000000; i++) //扫描,O(N)。
{
// 一次遍历,因为知道最小是220和284因此从220开始
if (sum[i] > i && sum[i] <= 5000000 && sum[sum[i]] == i)
{
//去重,不越界,满足亲和
printf("%d %d/n",i,sum[i]);
}
}
return 0;
}
好吧我想说这个算法碉堡
具体思路:
因为所有数的真因数都包含1,所以,先在各个数的下方全部置1
- 然后取i=2,3,4,5(i<=10/2),j依次对应的位置为j=(4、6、8、10),(6、9),(8),(10)各数所对应的位置。
- 依据j所找到的位置,在j所指的各个数的下面加上各个真因子i(i=2、3、4、5)。
整个过程,即如下图所示(如sum[6]=1+2+3=6,sum[10]=1+2+5=8.):
1 2 3 4 5 6 7 8 9 10
1 1 1 1 1 1 1 1 1 1
2 2 2 2
3 3
4
5 - 然后一次遍历i从220开始到5000000,i每遍历一个数后,
将i对应的数下面的各个真因子加起来得到一个和sum[i],如果这个和sum[i]==某个i’,且sum[i‘]=i,
那么这两个数i和i’,即为一对亲和数。 - i=2;sum[4]+=2,sum[6]+=2,sum[8]+=2,sum[10]+=2,sum[12]+=2...
i=3,sum[6]+=3,sum[9]+=3...
...... - i=220时,sum[220]=284,i=284时,sum[284]=220;即sum[220]=sum[sum[284]]=284,
得出220与284是一对亲和数。所以,最终输出220、284,...
orz
===================================Modified on 28th,April,2014========================
最近几个月,看了不少博客和书,发现之前参考的这个JULY这个人充其量是个搬运工,而且在搬运的时候都会自作聪明的不把这些知识来源著名出处,让人以为是他自己的,呵呵一下他,虽然资源整合也辛苦,但是,呵呵
就上面他对这个问题的描述就很不严谨,首先,我跑下来发现6和6也是一对亲和数,但是又看了别人的提示,亲和数是互不相等的两个数,而那个楼主的代码里面直接就写了因为知道最小是220和284因此从220开始
还有看这个博主之前的SVM的整理以及很多东西,其实现在看的书和博客更多更专业以后,发现他讲的,实在是,呵呵
贴一下自己实现的亲和数代码吧
PS要看,就要看专业的博客,这种半吊子,容易误导人
nums=[1]*5000000
size=5000000
def qinhe():
for i in range(2,5000000):
start = i*2-1;
while start<size:
nums[start]+=i
start+=i
for i in range(size):
a=nums[i]
if a>=size:
continue
if i+1 == nums[a-1] and a-1>i:
print(str(i+1)+" and "+str(a))
===================================Modified on 28th,April,2014========================