找回密码
 欢迎注册
楼主: 无心人

[讨论] 埃及分数问题

[复制链接]
发表于 2008-3-10 08:08:32 | 显示全部楼层
用你的方法,仅仅为了计算精度问题,在LCM大于64位整数的情况,可以采用多个互素的整数,
比如取三个互素的32位整数p1,p2,p3,使得p1*p2*p2大于LCM.
然后所有的求和过程改成求和并分别对p1,p2,p3求模(实际上如果只有加法,那么除法运算都不需要)。
当然,这样,搜索过程中和太大和太小本来可以提前剪枝的,这个方法不行。所以可以通过移位方法来做删减。
比如假设LCM比64位整数大8个比特,那么我们可以将所有的数除以256(分别求上取整和下取整)。在求和过程中同时计算这些上取整和下取整的和,如果发现它们越界,也可以提前裁减。

不过我上面介绍的方法,将会采用不同的方法。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-3-10 14:39:57 | 显示全部楼层
原帖由 无心人 于 2008-3-7 13:55 发表

这个题目要求技巧高些而已
还有输出多点
应该超过10万吧
:)


mathe 和 楼主, 能否告诉我, 你们预测的解的个数是多少个,如果解的个数太多,建议降低搜索层数,比如从16减低10或者更低。

我采用简单的剪枝方法(9#中的规则 “i)我们可以用部分和进行剪枝:”),用自定义的大整数验证,当搜索层数为 2-9是,解的个数为
level        解个数        时间
2        1               
3        3
4        12
5        117
6        1250
7        12986
8        126498   14 秒
9        1179414  567 秒

按此估计,即使以后每增加1层,解的个数增加5倍,最终解的个数也将超过90G, 而如果每增加1层,解的个数增加8倍,最终解的个数也将超过2000G。由于解的数目太多,将导致计算时间太长,验证也很困难。
顺便说一下,我在14楼和 20楼给出的程序 有bug,统计出的个数不对。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-3-10 15:52:09 | 显示全部楼层
你先剔除23, 29, 31, 37, 41, 47等大数字
2^6 * 3^4 * 5^2 * 7^2 * 11 * 13 * 17 * 19 = 2933 1862 5600
用2933 1862 5600  / n 代替 1 / n
则整个算法成为整数加的算法

然后将2-256中23, 29, 31, 37, 41, 43的倍数剔除
再剔除128, 256, 243, 125, 250几个
得到一个表
对每个数字n  , 使用2933 1862 5600  / n 代替

则使用穷举就能得到最后答案

只要注意当前面数字大到剩余数字用最小的1/255添充都太大
或者小到使用当前数字填充都太小
的时候就要终止

时间应该远小于你原来时间

至于添加23, 29, 31, 37, 41, 47的倍数的情况
mathe稍后有更好的算法
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-3-10 16:27:35 | 显示全部楼层
原帖由 liangbch 于 2008-3-10 14:39 发表


mathe 和 楼主, 能否告诉我, 你们预测的解的个数是多少个,如果解的个数太多,建议降低搜索层数,比如从16减低10或者更低。

我采用简单的剪枝方法(9#中的规则 “i)我们可以用部分和进行剪枝:”),用自定义 ...


我没有真正计算过,不过也感觉解的数目会太多。所以我首先的目标是就算解的数目,而不是将所有解输出。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-3-10 16:53:59 | 显示全部楼层
现在介绍一下我的思路,主要数学依据还是在36楼。
根据36楼,我们可以知道,比如对于所有分母为17的倍数的埃及分数: ${1/17, 1/{2*17}, ...,1/{15*17} }$
它们出现的模式(不考虑其它埃及分数是否出现)总共有1927种,比如:
$1/17(1/1+1/2+1/5)$
$1/17(1/1+1/4+1/6)$
$1/17(1/1+1/3+1/5+1/6)$
$1/17(1/1+1/2+1/3+1/4+1/7)$
$1/17(1/2+1/4+1/5+1/7)$
$1/17(1/2+1/6+1/7)$
等等
搜索这些模式的程序很简单
我们现在先考虑如何比较有效的保存它们。
对于每个模式,我们可以考虑用64比特信息保存
其中32比特用来保存模式中埃及分数和乘上720720的值(其实20比特就够了)
比如对于上面列出那些数据,结果分别为
72072
60060
72072
94380
46332
34320
另外我们需要知道每个数据使用了多少个埃及分数,这个数值不会超过15,所以4比特足够了,也采用8比特吧。
另外,我们希望在最后每个搜索结果出来后,可以很容易从模式还原会这些埃及序列,这个使用两部分数据表示
i) 模式是在那个基数的倍数,比如这里是17(这个数不会超过255,所以8比特足够)
ii)模式使用了基数的那些倍数,最多使用了15个倍数。所以我们可以采用15位比特位来表示
比如这里对于第一个模式,分别是17的1倍,2倍,5倍被使用了,所以我们可以采用二进制数0010011来表示这个部分
而我们还知道,对于27的倍数,还有一种特殊情况,就是还可以同时出现$1/81+1/162$,我们在添加一个比特位,比特位为真表示
还要添加$1/81+1/162$,所以这部分也使用16比特。

struct ELEMENT{
   unsigned value;
   unsigned char count;
   unsigned char base;
   unsigned short mask;
};
所以这里,第一个模式可以表示成
struct ELEMENT x={72072, 3, 17, 0010011};
而对于27的倍数的模式:
$1/27(1/1+1/2+1/4+1/5+1/7)+1/81+1/162$
可以表示为
struct ELEMENT x2={69212,7,27,0x805B};
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-3-10 16:59:14 | 显示全部楼层
27应该对应模式应该是63个,而不是64个,上面写错了,下面代码可以打印出所有这些模式:
  1. // flter.cpp : Defines the entry point for the console application.
  2. //

  3. #include "stdafx.h"
  4. #define OUTPUT

  5. int mask[]={1,1,2,6,12,60,60,420,840,2520,2520,27720,27720,360360,360360,360360,720720};
  6. int cands[]={17,19,23,29,31,37,41,43,47,53,59,61};
  7. int c2[]={5,7};
  8. int c3[]={1,2,4,5,6,7,2};
  9. #define NUMOF(x)  (sizeof(x)/sizeof(x[0]))
  10. int _tmain(int argc, _TCHAR* argv[])
  11. {
  12.     int i,j;
  13.     for(i=0;i<NUMOF(cands);i++){
  14.         int p=cands[i];
  15.         int mt=256/p;
  16.         int count=0;
  17.         int lm=mask[mt];
  18.         for(j=1;j<(1<<mt);j++){
  19.             int sum=0;
  20.             int k;
  21.             for(k=0;k<mt;k++){
  22.                 if(j&(1<<k)){
  23.                     sum+=lm/(k+1);
  24.                     sum%=p;
  25.                 }
  26.             }
  27.             if(sum==0){
  28.                 int start=1;
  29.                 count++;
  30. #ifdef OUTPUT
  31.                 for(k=0;k<mt;k++){
  32.                     if(j&(1<<k)){
  33.                         if(!start)fprintf(stderr,"+");
  34.                         fprintf(stderr,"1/%d",(k+1)*p);
  35.                         start=0;
  36.                     }
  37.                 }
  38.                 fprintf(stderr,"\n");
  39. #endif
  40.             }
  41.         }
  42.         printf("Count %d for prime %d\n",count,p);
  43.     }

  44.     for(i=0;i<NUMOF(c2);i++){
  45.         int p=c2[i];
  46.         int p2=p*p;
  47.         int count=0;
  48.         int mt=256/p2;
  49.         int lm=mask[mt];
  50.         for(j=1;j<(1<<mt);j++){
  51.             int sum=0;
  52.             int k;
  53.             for(k=0;k<mt;k++){
  54.                 if(j&(1<<k)){
  55.                     sum+=lm/(k+1);
  56.                     sum%=p;
  57.                 }
  58.             }
  59.             if(sum==0){
  60.                 int start=1;
  61.                 count++;
  62. #ifdef OUTPUT
  63.                 for(k=0;k<mt;k++){
  64.                     if(j&(1<<k)){
  65.                         if(!start)fprintf(stderr,"+");
  66.                         fprintf(stderr,"1/%d",(k+1)*p2);
  67.                         start=0;
  68.                     }
  69.                 }
  70.                 fprintf(stderr,"\n");
  71. #endif
  72.             }
  73.         }
  74.         printf("Count %d for %d\n",count,p2);
  75.     }

  76.     {
  77.         int p=3;
  78.         int p3=27;
  79.         int mt=7;
  80.         int count=0;
  81.         int lm=mask[mt];
  82.         for(j=1;j<(1<<mt);j++){
  83.             int sum=0;
  84.             int k;
  85.             for(k=0;k<mt;k++){
  86.                 if(j&(1<<k)){
  87.                     sum+=lm/c3[k];
  88.                     sum%=p;
  89.                 }
  90.             }
  91.             if(sum==0){
  92.                 int start=1;
  93.                 count++;
  94. #ifdef OUTPUT
  95.                 for(k=0;k<mt;k++){
  96.                     if(j&(1<<k)){
  97.                         if(!start)fprintf(stderr,"+");
  98.                         if(k==mt-1){
  99.                             fprintf(stderr,"1/81+1/162");
  100.                         }else{
  101.                             fprintf(stderr,"1/%d",c3[k]*p3);
  102.                         }
  103.                         start=0;
  104.                     }
  105.                 }
  106.                 fprintf(stderr,"\n");
  107. #endif
  108.             }
  109.         }
  110.         printf("Count %d for 27\n",count);
  111.         lm = 1*3*5*7;
  112.         count=0;
  113.         for(j=1;j<16;j++){
  114.             int sum=0;
  115.             int k;
  116.             for(k=0;k<4;k++){
  117.                 if(j&(1<<k)){
  118.                     sum+=lm/(2*k+1);
  119.                     sum%=2;
  120.                 }
  121.             }
  122.             if(sum==0){
  123.                 int start=1;
  124.                 count++;
  125. #ifdef OUTPUT
  126.                 for(k=0;k<4;k++){
  127.                     if(j&(1<<k)){
  128.                         if(!start)fprintf(stderr,"+");
  129.                         fprintf(stderr,"1/%d",(2*k+1)*32);
  130.                         start=0;
  131.                     }
  132.                 }
  133.                 fprintf(stderr,"\n");
  134. #endif
  135.             }
  136.         }
  137.         printf("Count %d for 32\n",count);
  138.     }
  139.         return 0;
  140. }
复制代码
我们首先需要将它们转化为我上面说明的结构。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-3-10 17:09:35 | 显示全部楼层
上面程序还有64的倍数$1/64+1/{3*64}$没有输出
这样我们总共得到13个长度非0的模式列表,此外还有77个720720的因子,每个单独构成一个模式列表。
所以总共90个模式列表。
其中有三个模式列表最长,分别为
17的倍数1927个模式L17
19的倍数430个模式L19
25的倍数255个模式L25
对于上面三个模式列表,我们在列表末尾添上一个特殊数据ELEMENT{0,0,*,0};
让后穷举它们的组合,并且定义一个长度为16*720720的数组tc[720720][16];
  1. memset(tc,0,sizeof(tc));
  2. for(i1=0;i1<1928;i1++)for(i2=0;i2<431;i2++)for(i3=0;i3<260;i3++){
  3.     int count=L17[i1].count+L19[i2].count+L25[i3].count;
  4.    int sum=L17[i1].value+L19[i2].value+L25[i3].value;
  5.    if(0<count&&count<=16&&sum<=720720){
  6.         tc[sum-1][count-1]++;
  7.    }
  8. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-3-10 17:22:26 | 显示全部楼层
然后对于余下的87个模式列表,我们按任意顺序事先排列好,假设为
ELEMENT *L[87];
其中每项模式数目为int c[87];
然后我们同样创建另外一个数组tc2[720720][16];
后面的程序中,需要频繁交换两个数组的位置,我们可以采用来个指针来轮换它们地位,具体做法这里就不写出了。
现在假设tc数组中已经保存了使用前3个模式列表在加上87个模式列表中t个模式列表(0<=t<87)中的模式使用了k个数达到和x的数目保存在tc[x-1][k-1]中
那么我们可以使用动态规划计算再加上的t个模式后的结果为
  1. memcpy(tc2,tc,sizeof(tc2));
  2. for(i=0;i<c[t];i++){
  3.     int c=L[t][i].count;
  4.     int v=L[t][i].value;
  5.    for(j=c;j<=16;j++)for(s=v;s<=720720;s++){
  6.       tc2[s][j-1]+=tc[s-v][j-c];
  7.    }
  8. }
复制代码
这个过程对于所有的t处理完的复杂度可以估计为16*720720*Sum(c[.])=16*720720*(88+8+6+1+1+1+4+3+7+1+77)=2.27G,现在的计算机处理起来应该很快。
全部计算完以后,那么tc2[720719][0..15]就保存了分别使用1,2,...,16个埃及分数达到1的组合确切数目。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-3-10 17:39:38 | 显示全部楼层
当然上面的过程,如果我们使用了更大的内存,显然不难可以计算出16个以上埃及分数和为1的确切数目(主要问题在于内存使用量)。
现在我们开始考虑另外一个问题,如果我们需要将所有组合都输出,那么使用上面的方法,是否可以提供一种非常有效的方法。
我们想象,如果我们能够将上面计算过程中的所有tc2数组的内容都保存下来(共88个这么大的数组),然后我们再使用动态规划的方法,反向再做一次(这次的目的是将那些不能达到1的中间状态全部找到)
比如已经使用了87个模式列表,得到一个tc[.][.],再使用最后一个模式列表的时候,我们应该可以发现,对于很多tc[][]中的数据,根本对数据tc2[720720-1][16-1]没有贡献,对于那些项,我们完全可以将它们的计数置0.这个方向过程可以将大量计数置0。有了这个过程以后,那么接下去穷举过程中,我们可以直接用这些tc2[.][.]数组作为指导,如果穷举到某层,对应的状态达到的计数tc2[.][.]是0,那么对于这个分支,我们就没有必要穷举下去。反之,如果对应计数非0,说明通过这个分支继续下去必然有解,所以这种剪枝方法是非常有效的(只要没有被剪掉的分支,必然继续穷举下去还有解),几乎没有任何浪费。
可惜的是数组这88个tc2[][]数组都很大,几乎不可能保存下来。
不过根据前面的分析,上面计算过程中,实际上我们关心的只是每个tc2[.][.]是否是0,而对于它具体的值并不关心。所以实际上,我们只需要保存88个长度为720720*16的比特向量就可以了。而88个长度为720720*16的比特向量占用的内存空间为88*720720*16/32=31.7M,这个内存空间一点也不大。
所以我们可以发现,这种方法完全可行。
而且实际计算过程中,我们更本不需要计算tc2[.][.],而是直接计算这些比特向量就行了,只要将上面所有用到的代码中+改成|,那么整个动态规划的正向过程一点也不需要改变。同样,对于动态规划的逆向过程,代码没有任何变化,只是操作的对象由整数数组改成比特数组了。
至于这个计算过程的速度如何,我没有实际实现过,无法评估(这个跟实际数据量有关系),但是应该已经是非常有效的方法。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-3-10 18:00:32 | 显示全部楼层
有谁有兴趣实现一下上面的算法(分别实现计数和搜索的代码)?
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

小黑屋|手机版|数学研发网 ( 苏ICP备07505100号 )

GMT+8, 2024-5-21 19:36 , Processed in 0.044802 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表