經典問題:木棒(搜索+強力剪枝)

木棒
Time Limit: 1000MS
Memory Limit: 10000K
Total Submissions: 95421
Accepted: 21444

Description

喬治拿來一組等長的木棒,將它們隨機地砍斷,使得每一節木棍的長度都不超過50個長度單位。然後他又想把這些木棍恢復到爲裁截前的狀態,但忘記了初始時有多少木棒以及木棒的初始長度。請你設計一個程序,幫助喬治計算木棒的可能最小長度。每一節木棍的長度都用大於零的整數表示。

Input

輸入包含多組數據,每組數據包括兩行。第一行是一個不超過64的整數,表示砍斷之後共有多少節木棍。第二行是截斷以後,所得到的各節木棍的長度。在最後一組數據之後,是一個零。

Output

爲每組數據,分別輸出原始木棒的可能最小長度,每組數據佔一行。

Sample Input

9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

Sample Output

6
5

Source

Translator

北京大學程序設計實習, Xie Di
==================================================================
這曾經是我省的省選題,當初做的時候一直沒A,今天刷題庫看到了,搜了下相關內容,終於搞懂了~
==================================================================
資料(來自http://blog.163.com/xdu_cfcry/blog/static/1694623032010718274132/):
==================================================================
   經典搜索題,黑書上的剪枝例題。剪枝的空間很大,剪枝前寫下樸素的搜索框架(黑字部分),枚舉原始木棍的長度及由那些小木棍組合。原始木棍長度一定是小木棍總長度的約數,因此可以減少枚舉量。

          越長的木棍對後面木棍的約束力越大,因此要把小木棍排序,按木棍長度從大到小搜索,這樣就能在儘可能靠近根的地方剪枝。(剪枝一)

          如果當前木棍能恰好填滿一根原始木棍,但因剩餘的木棍無法組合出合法解而返回,那麼讓我們考慮接下來的兩種策略,一是用更長的木棍來代替當前木棍,顯然這樣總長度會超過原始木棍的長度,違法。二是用更短的木棍組合來代替這根木棍,他們的總長恰好是當前木棍的長度,但是由於這些替代木棍在後面的搜索中無法得到合法解,當前木棍也不可能替代這些木棍組合出合法解。因爲當前木棍的做的事這些替代木棍也能做到。所以,當出現加上某根木棍恰好能填滿一根原始木棍,但由在後面的搜索中失敗了,就不必考慮其他木棍了,直接退出當前的枚舉。(剪枝二)

         顯然最後一根木棍是不必搜索的,因爲原始木棍長度是總木棍長度的約數。(算不上剪枝)

         考慮每根原始木棍的第一根木棍,如果當前枚舉的木棍長度無法得出合法解,就不必考慮下一根木棍了,當前木棍一定是作爲某根原始木棍的第一根木棍的,現在不行,以後也不可能得出合法解。也就是說每根原始木棍的第一根小木棍一定要成功,否則就返回。(剪枝四)

         剩下一個通用的剪枝就是跳過重複長度的木棍,當前木棍跟它後面木棍的無法得出合法解,後面跟它一樣長度的木棍也不可能得到合法解,因爲後面相同長度木棍能做到的,前面這根木棍也能做到。(剪枝五)

         這樣剪枝基本就結束了,我們發現,每種剪枝都只是加了一條語句,但剪枝效果非常明顯。uva307題目跟這個一模一樣,當時uva的數據規模更強,許多在poj 10+MS的程序在uva 3000MS都跑不出來,當時我的加上這些剪枝,我的程序在uva也能跑出靠前的成績。  rank:38      time:0.244s

         剪枝要平衡準確性和額外花費的關係,一開始我用上下界剪枝,這個額外花費相當大,每次搜索一根原始木棍都要更新從某根木棍開始到最後一根可用木棍的總長度,對於一般的數據確實能跑的比沒加剪枝快,但對苛刻的,第一根木棍總不成功的數據,這個剪枝就成了程序的瓶頸,導致我在poj都超時了,刪除後0MS, 汗~~。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int used[100],len[100],sum,n,Min,s[100];
int find(int p,int rest,int trest)
{
    int i;
    if (trest==Min) return 1;
    for (i=p;i<=n;i++)
       if (!used[i]&&len[i]<=rest)
       {
          used[i]=1;
          if (len[i]==rest)
          {
             if (find(1,Min,trest-len[i])) return 1;
          } else if (find(i+1,rest-len[i],trest-len[i])) return 1;
          used[i]=0;
          if (len[i]==rest) return 0;
          if (trest==sum) return 0;
          if (rest==Min) return 0;
          while (len[i+1]==len[i]) i++;
       }
    return 0;
}
bool cmp(int a,int b) {return a>b;}
int main()
{
    int i;
    while (scanf("%d",&n)&&n!=0)
    {
       for (i=1;i<=n;i++)
           scanf("%d",&len[i]);
       memset(used,0,sizeof(used));
       sort(len+1,len+n+1,cmp);
       sum=0;
       for (i=1;i<=n;i++) sum+=len[i];
       Min=len[1];
       while (sum%Min!=0) Min++;
       s[n+1]=0;
       while (find(1,Min,sum)==0)
       {
          Min++;
          while (sum%Min!=0) Min++;
       }
       printf("%d\n",Min);
    }
    return 0;
}
==================================================================
看完這個我終於會了,有些強力剪枝真是想不到啊~
==================================================================
貼個代碼:
var
n,m,tot,i:longint;
a:array[1..65] of longint;
v:array[1..65] of boolean;

function dfs(nn,p,k,total:longint):boolean;
var i:longint;
begin
     if nn=0 then exit(true);
     i:=1;
     while i<=n do begin
         if (not v[i])and(a[i]+total<=m) then begin
            v[i]:=true;
            if a[i]+total=m then dfs:=dfs(nn-1,p+1,1,0)
            else dfs:=dfs(nn-1,p,k+1,total+a[i]);
            v[i]:=false;

            if dfs then exit(true);
            if (a[i]+total=m)or(k=1) then exit(false);
            while(i<n)and(a[i+1]=a[i])do inc(i);
         end;
         inc(i);
     end;
     exit(false);
end;

procedure sort(l,r:longint);
var i,j,mid,tmp:longint;
begin
     i:=l; j:=r; mid:=a[(l+r)shr 1];
     repeat
           while a[i]>mid do inc(i);
           while a[j]<mid do dec(j);
           if i<=j then begin
              tmp:=a[i]; a[i]:=a[j]; a[j]:=tmp;
              inc(i); dec(j);
           end;
     until i>j;
     if i<r then sort(i,r);
     if l<j then sort(l,j);
end;

begin
     while true do begin
           readln(n); if n=0 then break;

           tot:=0;
           for i:=1 to n do begin
               read(a[i]);
               inc(tot,a[i]);
           end;

           sort(1,n);
           fillchar(v,sizeof(v),false);
           for m:=a[1] to tot do
               if tot mod m =0 then
               if dfs(n,1,1,0) then begin
                  writeln(m);
                  break;
               end;
     end;
end.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章