今天搞了一下傳說中的經典搜索題——poj1011,果然里面充斥着各種巧妙的剪枝,做完之后回味一下還是感覺構思太巧妙,所以總結記錄一下加深理解。
原題:http://poj.org/problem?id=1011
剛開始接觸搜索的初學者面對這道題可能感覺無從下手,即便是告訴了要用深搜解決這道題,也不知道怎么用,我現在也對搜索有了更多的理解與體會,其實不要把搜索只理解為在一個地圖上找點,其實搜索更可以抽象為當面對多個選擇的時候如何抉擇,深搜就是先認准一個方向走下去,不行再回來,走別的路;廣搜就是把每一次能做的選擇都試一遍。所以,這道拼木棍的題就是一道很好的深搜題。我們不知道哪根木棍可組合進來,那不如就按從前往后的順序一個一個試。
嘮叨了半天,終於該放代碼了。。簡單的注釋我寫在程序里了,稍微長一點的解釋,我程序里有序號,我把詳細的解釋寫在外面,大家對一下序號來看。
PS:我不能確定我寫的已經是優化到最好了,也希望有更好想法的小伙伴可以在評論里教下我,謝謝~如果喜歡別忘了點贊喲~
- 這里要解釋的是sticks[i]!=pre,假設有幾根木棍是5 5 5 2,這時,若確定第一根已經不滿足條件了,后面兩根長度為5的木棍也沒有必要去試了,所以跳過就好了。這里可能會有小伙伴理解為這樣會導致不能正確的加入兩根相同的木棍(我最初就這樣想的。。),比如5 2 5 1 1,會理解為最后一個1加不上了。其實當第一個1符合條件的情況下,函數會再次調用,所以新調用的函數了pre又是0了,所以這個條件完美排除了第一根就不符合條件的情況,並沒有影響重復長度木棍第一根符合要求的情況。
- 這里dfs的返回值若為1,說明已經完成題目要求了(前面只有cnt==num才返回1),所以可以跳出循環,這會影響后面if語句的判斷,若是跳出的,說明找到了,若不是跳出,正常結束循環,說明沒有找到。
- 這個剪枝也很巧妙,它是考慮了,當我每合並一個新木棍時,第一根木棍是很特殊的,我們每次從前往后挑選木棍時,總是選沒用過且長度符合要求的,這也就意味着,當我們選中了第一根木棍時,它的長度是符合要求的,這一根是一定要用的(無論對於哪個合成的木棍,這根都會是作為第一根用,這里大家好好琢磨一下),所以若前面的if語句沒執行,就會執行這一句,而且滿足k==0,說明后面的木棍都沒法和這第一根木棍組成新木棍了,所以可以直接退出了(盡量解釋了,大家好好想下)
1 #include<stdio.h> 2 #include<string.h> 3 int sticks[70],book[70];//一個存木棍,一個標記是否使用 4 int n,len,num;//len是合並后每根木棍的長度,num是合並后的木棍數目 5 //手寫快排,不太會C++。。。 6 void quicksort(int left,int right){ 7 if(left>right) 8 return; 9 int temp=sticks[left],i=left,j=right; 10 while(i!=j){ 11 while(sticks[j]<=temp&&j>i) 12 j--; 13 while(sticks[i]>=temp&&j>i) 14 i++; 15 if(i<j){ 16 int t=sticks[i]; 17 sticks[i]=sticks[j]; 18 sticks[j]=t; 19 } 20 } 21 sticks[left]=sticks[i]; 22 sticks[i]=temp; 23 quicksort(left,i-1); 24 quicksort(i+1,right); 25 } 26 int dfs(int cur,int k,int cnt){//cur是正在合並的木棍的長度,k是木棍的下標,cnt是合並好的木棍數 27 if(cnt==num)//完成要求的情況 28 return 1; 29 if(cur==len)//合並好一根木棍的情況 30 return dfs(0,0,cnt+1); 31 int i,pre=0;//i是木棍下標,pre保存重復木棍 32 for(i=k;i<n;i++){ 33 if(book[i]==0&&sticks[i]+cur<=len&&sticks[i]!=pre){//1 34 pre=sticks[i]; 35 book[i]=1; 36 if(dfs(sticks[i]+cur,i+1,cnt))//2 37 break; 38 book[i]=0; 39 if(k==0)//3 40 return 0; 41 } 42 } 43 if(i==n) 44 return 0; 45 else 46 return 1; 47 } 48 int main(){ 49 while(scanf("%d",&n)!=EOF&&n){ 50 int sum=0;//總長度 51 for(int i=0;i<n;i++){ 52 scanf("%d",&sticks[i]); 53 sum+=sticks[i]; 54 } 55 quicksort(0,n-1);//注意要從大到小排序,因為合並后木棍的長度一定大於原來最長的 56 for(len=sticks[0];len<=sum/2;len++){//剪枝,從最大的長度開始枚舉,這里大於sum/2歸並為了合成一根木棍的情況 57 if(sum%len==0){//長度是總長因數才符合要求 58 num=sum/len; 59 memset(book,0,sizeof(book)); 60 if(dfs(0,0,0)) 61 break; 62 } 63 } 64 if(len>sum/2)//一根木棍的情況 65 printf("%d\n",sum); 66 else 67 printf("%d\n",len); 68 } 69 return 0; 70 }