埃及分數 ----- 迭代加深搜索


題目:埃及分數

題目鏈接:http://codevs.cn/problem/1288/

題目大意:

  給出一個分數,由分子a 和分母b 構成,現在要你分解成一系列互不相同的單位分數(形如:1/a,即分子為1),要求:分解成的單位分數數量越少越好,如果數量一樣,最小的那個單位分數越大越好。

如:

  19/45 = 1/3 + 1/12 + 1/180;

  19/45 = 1/5 + 1/6 + 1/18;

  以上兩種分解方法都要3個單位分數,但下面一個的最小單位分數1/18比上一個1/180大,所以第二個更優。

思路:

  雖然是求最優解,但這道明顯不是廣搜吧(空間要求太高),而且很明顯是用深搜做,即從1~無窮,每一個分母,都有選中和不選中兩種狀態,如果選中,那么就減去這個分數,沒有就是跳過,但無窮到底是哪里呢,而且具體要選幾個呢,這就是這道題的難點。因為多組答案的優先級是由單位分數的個數首先決定,那么我們可以逐次放寬個數的限制,即迭代加深搜索。

  迭代加深搜索的具體內容:第一次,我限制只能用k個單位分數來完成,如果找完所有情況,還是沒找到解,那么我現在用k+1個單位分數解決,這樣優點如下:

  1. 肯定保證最優解,因為我用k個來完成分解就說明比k小的個數分解不出來,如果分解得出來,我在那時就退出循環了。

  2. 雖然看似重復進行了很多遍的搜索,但上一層的搜索量和下一層比起來太少了,不影響總的時間復雜度。

拋開逐步解開個數限制外,每一個個數限制下的做法和平常的深入優先搜索大致相同,要注意剪枝!

主要的兩個剪枝如下:

  1. 限制開頭:並不是每次都要從1開始遍歷分母,假設現在要分解a/b,那么分母b/a就是起點,因為b/a的分數太大,起始點已經超過了a/b,沒有什么意義:1/(b/a)=a/b ,假設起點s<b/a,那么顯而易見,起點的分數已經比我們要的總和(a/b)大了。

  2. 限制結尾:

    (1)比較簡單的限制結尾可以這樣看:如果我已經找到分母k了,而現在要分解得分數是a/b,現在還要找m個單位分數,那么可以想象:有可能 m * ( 1/k ) 還小於a/b,也就是說就算全是1/k,我湊夠m個,也達不到a/b,那么說明前面的分配方案肯定有無,直接可以return了。加上這個剪枝已經可以得到答案了,只是時間有點慢罷了。

    (2)現在我們假設終點為t,還要找m個單位分數,現在的分數剩下a/b,那么很容易有m * (1/t) <= a / b  ,也就是說我如果m個都用最小的,肯定小於等於a/b。(等於號就是說有可能m=1,我可能直接終點就是答案,如果m>1,那么終點肯定也不可能選到,假設選了(后面還要選(m-1)個,肯定湊不夠)這樣子,由上面的式子,經過變換,可以得到 t >= m*b/a ,也就是說終點為m*b/a。

有了思路代碼還是很容易的:

 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<stdlib.h>
 4 
 5 int gcd(int a,int b)
 6 {
 7   return b?gcd(b,a%b):a;
 8 }
 9 
10 int ans[1000],ao;
11 int out[1000],oo;
12 
13 void dfs(int limit,int h,int ma,int mb)
14 {
15   if(h==limit) return ;
16   if(mb%ma==0 && mb/ma>ans[ao-1] && ( oo<=0 || mb/ma < out[oo-1] ))
17   {
18     ans[ao++]=mb/ma;
19     oo=ao;
20     memcpy(out,ans,sizeof(ans));
21     ao--;
22     return ;
23   }
24   int i=mb/ma-1;
25   if(i<=ans[ao-1]) i=ans[ao-1]; //ans[ao-1]就是前面找過的最后一個,這前面的都處理過(選中or不選中)
26   int j=(limit-h)*mb/ma;
27   while(++i<=j)
28   {
29     if(oo>0&&i>=out[oo-1]) return ;
30     int g=gcd(i,mb);
31     int k=i/g;
32     //if(ma*i/mb+h>limit) return ;
33     int x=mb*k;
34     int y=ma*k-mb/g;
35     if(y<0) continue;
36     ans[ao++]=i;
37     if(y==0)
38     {
39       oo=ao;
40       memcpy(out,ans,sizeof(ans));
41       ao--;
42       return ;
43     }
44     dfs(limit,h+1,y,x);
45     ao--;
46   }
47 }
48 
49 int main()
50 {
51   int a,b;
52   while(scanf("%d%d",&a,&b)!=EOF)
53   {
54     ao=0;
55     oo=0;
56     for(int i=1;i<100;i++)
57     {
58       //printf("%d\n",i);
59       dfs(i,0,a,b);
60       if(oo>0) break;
61     }
62     for(int i=0;i<oo;i++)
63     {
64       if(i!=0) printf(" ");
65       printf("%d",out[i]);
66     }
67     printf("\n");
68   }
69   return 0;
70 }
AC代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM