POJ 1011 Sticks


地址:http://poj.org/problem?id=1011

問題描述:

喬治拿來一組等長的木棒,將它們隨機地裁斷,使得每一節木棒的長度都不超過50個長度單位。然后他又想把這些木棒恢復到為裁截前的狀態,但忘記了木棒的初始長度。請你設計一個程序,幫助喬治計算木棒的可能最小長度。每一節木棒的長度都用大於零的整數表示
 
輸入:由多個案例組成,每個案例包括兩行。第一行是一個不超過64的整數,表示裁截之后共有多少節木棒。第二行是經過裁截后,所得到的各節木棒的長度。在最后一個案例之后,是零。
 
輸出:為每個案例,分別輸出木棒的可能最小長度。每個案例占一行。
 

思路1:

題意: 給你n 木棍碎片,要求還原原木棍,且原木棍等長,問最短長度是多少。

分析: 各種剪枝

 

 1 /*1:越長的木棍對后面木棍的約束力越大,因此要把小木棍排序,
 2     按木棍長度從大到小搜索,這樣就能在盡可能靠近根的地方
 3     剪枝。
 4 :當出現加上某根木棍恰好能填滿一根原始木棍,但由在后面的
 5     搜索中失敗了,就不必考慮其他木棍了,直接退出當前的枚舉。
 6 :考慮每根原始木棍的第一根木棍,如果當前枚舉的木棍長度無
 7     法得出合法解,就不必考慮下一根木棍了,當前木棍一定是作
 8     為某根原始木棍的第一根木棍的,現在不行,以后也不可能得
 9     出合法解。也就是說每根原始木棍的第一根小木棍一定要成
10     功,否則就返回。
11 :剩下一個通用的剪枝就是跳過重復長度的木棍,當前木棍跟
12     它后面木棍的無法得出合法解,后面跟它一樣長度的木棍也
13      不可能得到合法解,因為后面相同長度木棍能做到的,前面這
14      根木棍也能做到。
15  */
16 #include<stdio.h>
17 #include<string.h>
18 #include<stdlib.h>
19 int cmp(const void*p1,const void*p2)
20 {
21     return *(int*)p2-*(int*)p1;
22 }
23 int a[100],v[100];
24 int n,len,s,tot,min;
25 bool dfs(int k,int mi,int left)
26 {
27     int i;
28     if(left==min) 
29         return 1;
30     for(i=k;i<=n;i++)
31         if(!v[i]&&a[i]<=mi)
32         {
33             v[i]=1;
34             if(a[i]==mi&&dfs(1,len,left-a[i]))
35                 return 1;
36             else if(dfs(i+1,mi-a[i],left-a[i]))
37                 return 1;
38             v[i]=0;
39             if(a[i]==min)return 0;
40             if(left==tot)return 0;
41             if(mi==len)  return 0;
42             while(a[i+1]==a[i])
43                 i++;
44         }
45     return 0;
46 }
47 int main()
48 {
49     int i,res;
50     while(scanf("%d",&n),n)
51     {
52         tot=0;
53         for(i=1;i<=n;i++){
54             scanf("%d",&a[i]);
55             tot+=a[i];
56         }
57         qsort(a+1,n,sizeof(a[0]),cmp);
58         len=a[1];
59         res=len;
60         memset(v,0,sizeof(v));
61         res=tot;
62         for(;len<tot;len++)
63             if(tot%len==0&&dfs(1,len,tot))
64             {
65                 res=len;
66                 break;
67             }
68         printf("%d\n",res);
69     }
70     return 0;
71 }

 

 

思路2:

               思想很簡單,一個接一個的把木棍拼起來,最后把木棍用光。
*             關鍵的地方是幾個剪枝技巧:
*                   設所有木棍的總長度為 Sum, 最終的答案(長度)是 L。 
*             1. 首先要明白, Sum一定要能被 L 整除。 
*             2. L 一定 大於等於 題目給出的最長的木棍的長度 Max。
*                  由上述兩點,我們想到,可以從 Max 開始遞增地枚舉 L, 
*                直到成功地拼出 Sum/L 支長度為 L 的木棍。
*                    搜索種的剪枝技巧: 
*             3. 將輸入的輸入從大到小排序,這么做是因為一支長度為 K 
*                的完整木棍,總比幾支短的小木棍拼成的要好。
*                形象一些:
*                  如果我要拼 2 支長為8的木棍,第一支木棍我拼成 
*                          5 + 3
*                  然后拼第二支木棍但是失敗了,而我手中還有長為 2 和 1 
*                  的木棍,我可以用 5 + 2 + 1 拼好第一支,再嘗試拼第二
*                  支,仔細想一想,就會發現這樣做沒意義,注定要失敗的。     
*                  我們應該留下 2+1 因為 2+1 比 3 更靈活。 
*             4. 相同長度的木棍不要搜索多次, 比如:
*                我手中有一些木棍, 其中有 2 根長為 4 的木棍, 當前搜索
*                狀態是 5+4+.... (即表示長度為 5,4,2 的三支拼在一起, 
*                ...表示深層的即將搜索的部分), 進行深搜后不成功,故我
*                沒必要用另一個 4 在進行 5+4+...
*             5. 將開始搜索一支長為 L 的木棍時,我們總是以當前最長的未
*                被使用的 木棍開始,如果搜索不成功,那么以比它短的開始
*                那么也一定不能取得全局的成功。因為每一支題目給出的木棍
*                都要被用到。
*                如果,有 
*                    4
*                    5 4 4 3 2
*                  想拼成長為 6 的木棍,那么從 5 開始, 但是顯然沒有能與 5
*                  一起拼成 6 的,那么我就沒必要去嘗試從 4 開始的,因為
*                  最終 5 一定會被遺棄。在拼第 2 3 ... 支木棍時,一樣。 
*             6. 最后的最簡單的一個就是,
*                      for(int i = 0; i < n; i++)
*                          for(int j = 0; j < n; j++)
*                               {}
*                與
*                      for(int i = 0; i < n; i++)
*                          for(int j = i+1; j < n; j++)
*                               {} 
*                的區別,這個不多說了。
*             7. 我用過的另一個剪枝,但是對 poj 的數據效果一般,
*                用一個數組, Sum[i] 保存 第 i 個木棍之后,即比第 i 枝
*                木棍短或與之相等所有的木棍的長度之和。
*                試想,如果剩余的所有木棍加在一起都不能和我當前的狀態拼
*                出一直長為 L 的木棍(從長度來看),還有必要搜下去么? 

代碼如下:

 1 #include <iostream>
 2 #include <string.h>
 3 #include <stdio.h>
 4 #include <algorithm>
 5 
 6 using namespace std;
 7 int sticks[65],n,sum,num,l;//l最小的與木棒的長度 num:圓木棒的個數  sum:圓木棒的總長度
 8 bool mark[65];
 9 bool cmp(int a,int b)
10 {
11     return a>b;
12 }
13 
14 //s:已經組成的小木棒的個數,le:當前搜索時正在組成的小木條的長度。pos:要組合的小木條的下標位置
15 bool dfs(int s,int le,int pos)
16 {
17     int i;
18     bool sign = (le == 0?true:false);
19     if(s==num)return true;
20     for(i = pos + 1;i < n;i++)
21     {
22         if(mark[i])continue;//如果這個小木棒組合過則從下一個開始
23         if(le + sticks[i]==l)//如果組合剛好組成l長度的木棒那么就要組合下一根木棒了即第s+1根
24         {
25             mark[i] = true;
26             if(dfs(s+1,0,-1))//第s根已經組合成功了組合下一根
27             return true;
28             mark[i] = false;
29             return false;//如果組合失敗了那么就要返回false 而且這個木棒的狀態應還原為沒組合過
30         }
31         else if(le + sticks[i]<l)
32         //如果組合當前這根后長度仍然小於l那么從i開始往下找繼續組合第S根木棒
33         {
34             mark[i] = true;
35             if(dfs(s,le+sticks[i],i))//如果可以組成就返回true
36             return true;
37             mark[i] = false;
38             if(sign)return false;
39             while(sticks[i]==sticks[i+1])i++;
40         }
41     }
42     return false;
43 }
44 
45 int main()
46 {
47     while(scanf("%d",&n)!=EOF,n)
48     {
49         sum = 0;
50 
51         for(int i = 0; i < n; i++)
52         {
53             scanf("%d",&sticks[i]);
54             sum += sticks[i];
55         }
56         sort(sticks,sticks+n,cmp);//將木棒從大到小排序
57         for(l = sticks[0]; l <= sum; l++)//從最大的開始搜索
58         {
59             if(sum%l==0)//如果該長度可以被整除那么可能是結果,否則繼續
60             {
61                 num = sum/l;//num:記錄該情況下可以組成的木棒的數目。
62                 memset(mark,false,sizeof(mark));//每種情況都要進行初始化,把所有的木棒的使用狀態設為false
63                 if(dfs(1,0,-1))//當返回true時說明搜索成功了,可以組成該長度木條
64                 {
65                     printf("%d\n",l);
66                     break;
67                 }
68             }
69         }
70     }
71     return 0;
72 }

 

思路3:

1:初始狀態:有N節木棒
最終狀態:這N節木棒恰好被拼接成若干根等長的木棒
從初始狀態到最終狀態最多有多少條不同的“路徑”?
Sum /maxParts。其中Sum是全部N節木棒的長度之和,maxParts是最長一節木棒的長度
每條“路徑”對應一個木棒的長度。從木棒長度最小的那條可能“路徑”開始,如果成功地的找到了這條“路徑”,就解決了問題
 
2:構造一條木棒長度為L的“路徑”:拼接木棒
在未被拼接的木棒中,找出一節最長的,開始拼接
從未拼接的木棒中,選擇一個長度合適的木棒,使得拼接后的木棒長度≤L
找到了
在前面的拼接過程中曾試圖用過相同長度的一節其他木棒,但發現這樣拼接不成功,繼續尋找能夠進行拼接的木棒
把找到的那節木棒拼接上去。繼續進行拼接
繼續拼接成功,找到了“路徑”
繼續拼接不成功,把剛拼接的那節木棒拿下來,繼續找下一個合適的未拼接木幫
沒有找到:拼接失敗
 
3:在已拼接部分未用長度為L1的木棒,則表明用長度為L1的木棒來拼接時是不成功的
下次拼接時也不能選擇長度為L1的木棒,而應該選擇長度為L2或L3的木棒
如果長度為L2或L3的木棒也不能選擇,則需要替換已拼接部分的最后一節木棒
 
代碼如下:
 1 #include <iostream.h>
 2 #include <memory.h>
 3 #include <stdlib.h>
 4 int T, S;
 5 int L;
 6 int anLength[65];
 7 int anUsed[65];
 8 int i,j,k;
 9 int Dfs(int nUnusedSticks, int nLeft);
10 int MyCompare( const void * e1, const void * e2) {
11     int * p1, * p2;
12     p1 = (int * ) e1;
13     p2 = (int * ) e2;
14     return * p2 - * p1;
15 }
16 main()
17 {
18     while(1) {
19         cin >> S;
20         if( S == 0 )
21             break;
22         int nTotalLen = 0;
23         for( int i = 0; i < S; i ++ ) {
24             cin >> anLength[i];
25             nTotalLen += anLength[i];
26         }
27         qsort(anLength,S,sizeof(int),MyCompare);
28 for( L = anLength[0]; L <= nTotalLen / 2; L ++ ) {
29         if( nTotalLen % L)
30             continue;
31         memset( anUsed, 0,sizeof(anUsed));
32         if( Dfs( S,L)) {
33             cout << L << endl;
34             break;
35         }
36      }
37     if( L > nTotalLen / 2 )
38         cout << nTotalLen << endl;
39     } // while
40 }
41 int Dfs( int nUnusedSticks, int nLeft)
42 // nLeft表示當前正在拼的棍子和 L 比還缺的長度
43 {
44     if( nUnusedSticks == 0 && nLeft == 0 )
45         return true;
46     if( nLeft == 0 ) //一根剛剛拼完
47         nLeft = L;  //開始拼新的一根
48     for( int i = 0;i < S;i ++) {
49         if( !anUsed[i] && anLength[i] <= nLeft) {
50             if( i > 0 ) {
51                 if( anUsed[i-1] == false 
52                    && anLength[i] == anLength[i-1])
53                     continue; //剪枝3
54             }
55             anUsed[i] = 1;
56 if ( Dfs( nUnusedSticks - 1,
57                  nLeft - anLength[i]))
58                 return true;
59             else {
60                 anUsed[i] = 0;//說明不能用i作為
61                         //第1條,
62                         //那么要拆以前的
63                         //棍子,i還可能用
64                         //在以前的棍子里,
65                          //因此要 anUsed[i] = 0;
66                 if( anLength[i] == nLeft || nLeft == L)
67                     return false;//剪枝2、1
68             }
69         }
70     }
71     return false;
72 }

思路4:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 int sticks[100],n;
 4 bool used[100];
 5 int cmp(const void *x,const void *y)
 6 {
 7     return *(int *)y - *(int *)x;
 8 }
 9 bool find(int left,int num,int len)
10 {
11     int i;
12     if(left == 0 && num == 0)
13     return 1;
14     if(left == 0)
15     left = len;
16     for(i = 0;i < n;i ++)
17     {
18         if(sticks[i] <= left && !used[i])
19         {
20             used[i] = 1;
21             if(find(left - sticks[i],num-1,len))
22             return 1;
23             used[i] = 0;
24             if(sticks[i] == left || left == len)
25             return 0;
26         }
27     }
28     return 0;
29 }
30  
31 int main()
32 {
33     int i,sum = 0;
34     while(scanf("%d",&n) != EOF && n)
35     {
36         sum = 0;
37         for(i = 0;i < n;i ++)
38         {
39             scanf("%d",&sticks[i]);
40             sum += sticks[i];
41             used[i] = 0;
42         }
43         qsort(sticks,n,sizeof(int),cmp);
44         for(i = sticks[0];i <= sum;i ++)
45         {
46             if((sum % i == 0) && find(i,n,i))
47             {
48                 printf("%d\n",i);
49                 break;
50             }
51         }
52     }
53     return 0;
54 }

 

 

 
 
 
 
 
 


免責聲明!

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



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