P1120/UVA307 小木棍(sticks) 题解
题目描述
题解
注意的问题是,各个原始木棒的长度都是一样的!
说一下本题的总思路即:DFS+超强力剪枝!(详见本人的 AC 程序)
首先,我们要从小到大枚举原始木棒的长度len,也就是枚举答案,最先找到的就是最小长度。那怎么确定枚举的是正确的呢?我们用的是搜索。
如果只搜索是极不理智的,而如果有了剪枝,一切就好说了,本题的剪枝主要有五个,我们会边看程序边了解一下。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 int len,n; 7 bool v[101]; 8 int a[101]; 9 int cnt,sum,val; 10 //stick 即正在拼第stick根木棒(确保前面的都拼好了) 11 //第stick根木棒的当前长度为cab 12 //拼第stick根木棒的上一根小木棒为last(有些小朋友可能会有疑问,为什么第一次搜索是dfs(1,0,1)而不是dfs(1,0,0)首先,如果dfs(1,0,0)也能过并且更正确一些,而即使dfs(1,0,1)在bfs中的第三个if中,因为v[i]==1也不会进入) 13 bool dfs(int stick,int cab,int last) 14 { 15 if(stick>cnt) 16 return true;//所有的木棒都已经拼好 17 if(cab==len) 18 return dfs(stick+1,0,1); //当前的一根已经拼好,开始下一根 19 int fail=0;//第二个剪枝开始了:对于每根木棒,fail记录的是最近一次尝试拼接的木棍长度。这样再回溯时就不会再尝试相同长度的木棍。 20 for(int i=last;i<=n;i++)//第三个剪枝开始了:限制先后加入一根原始木棍的长度是递减的。因为先拼上一个长为x的木棍再拼上一个长为y的木棍,等效于先拼上一个长为y的木棍再拼上一个长为x的木棍。所以只需要搜索其中一种即可。 21 { 22 if(v[i]==0&&a[i]+cab<=len&&fail!=a[i]) 23 { 24 v[i]=1; 25 if(dfs(stick,cab+a[i],i)) 26 return true; 27 v[i]=0;//还原搜索前的状态。 28 fail=a[i]; 29 if(cab==0||cab+a[i]==len) 30 return false;//第四个剪枝开始了:如果在一根原始木棒中尝试拼接的第一根木棍的递归分支就以失败返回,直接判断当前分支无解。与此同时,第五个剪枝开始了,如果两个木棍的长度和与一个木棍的一样,只尝试一个的就行了(因为前两个可能会有更大的效用) 31 } 32 } 33 return false;//所有分支都尝试过,搜索失败。 34 } 35 int main() 36 { 37 while(cin>>n&&n) 38 { 39 sum=0; 40 val=0; 41 for(int i=1;i<=n;i++) 42 { 43 scanf("%d",&a[i]); 44 sum+=a[i];//为啥要累加尼?为了求原始木棒的根数,即sum/len(len是枚举的答案) 45 val=max(val,a[i]); 46 } 47 sort(a+1,a+n+1);//第一个剪枝开始了:此为优化搜索顺序,优先尝试较长的木棍 48 reverse(a+1,a+n+1);//sort从小到大排序,而reverse会将整个数组翻转(懒人做法),这样就可以达到从大到小排的结果 49 for(len=val;len<=sum;len++) 50 { 51 if(sum%len) 52 continue; 53 cnt=sum/len; 54 memset(v,0,sizeof(v)); 55 if(dfs(1,0,1))//(1,0,1具体是啥,看上方的dfs) 56 { 57 cout<<len<<endl; 58 break;//确定枚举正确的第一次即为答案,立即结束 59 } 60 } 61 } 62 return 0; 63 }