POJ1011

今天搞了一下传说中的经典搜索题——poj1011,果然里面充斥着各种巧妙的剪枝,做完之后回味一下还是感觉构思太巧妙,所以总结记录一下加深理解。

原题:http://poj.org/problem?id=1011

刚开始接触搜索的初学者面对这道题可能感觉无从下手,即便是告诉了要用深搜解决这道题,也不知道怎么用,我现在也对搜索有了更多的理解与体会,其实不要把搜索只理解为在一个地图上找点,其实搜索更可以抽象为当面对多个选择的时候如何抉择,深搜就是先认准一个方向走下去,不行再回来,走别的路;广搜就是把每一次能做的选择都试一遍。所以,这道拼木棍的题就是一道很好的深搜题。我们不知道哪根木棍可组合进来,那不如就按从前往后的顺序一个一个试。

唠叨了半天,终于该放代码了。。简单的注释我写在程序里了,稍微长一点的解释,我程序里有序号,我把详细的解释写在外面,大家对一下序号来看。

PS:我不能确定我写的已经是优化到最好了,也希望有更好想法的小伙伴可以在评论里教下我,谢谢~如果喜欢别忘了点赞哟~

  1. 这里要解释的是sticks[i]!=pre,假设有几根木棍是5 5 5 2,这时,若确定第一根已经不满足条件了,后面两根长度为5的木棍也没有必要去试了,所以跳过就好了。这里可能会有小伙伴理解为这样会导致不能正确的加入两根相同的木棍(我最初就这样想的。。),比如5 2 5 1 1,会理解为最后一个1加不上了。其实当第一个1符合条件的情况下,函数会再次调用,所以新调用的函数了pre又是0了,所以这个条件完美排除了第一根就不符合条件的情况,并没有影响重复长度木棍第一根符合要求的情况。
  2. 这里dfs的返回值若为1,说明已经完成题目要求了(前面只有cnt==num才返回1),所以可以跳出循环,这会影响后面if语句的判断,若是跳出的,说明找到了,若不是跳出,正常结束循环,说明没有找到。
  3. 这个剪枝也很巧妙,它是考虑了,当我每合并一个新木棍时,第一根木棍是很特殊的,我们每次从前往后挑选木棍时,总是选没用过且长度符合要求的,这也就意味着,当我们选中了第一根木棍时,它的长度是符合要求的,这一根是一定要用的(无论对于哪个合成的木棍,这根都会是作为第一根用,这里大家好好琢磨一下),所以若前面的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
",sum);
66         else
67             printf("%d
",len);
68     }
69     return 0;
70 }
原文地址:https://www.cnblogs.com/zqy123/p/4921564.html