Sticks
http://poj.org/problem?id=1011
解题思路
1.一开始的思路是遍历木棒能组成的所有长度,然后进行许多判断;发现判断过程很复杂,做不了,就换了个思路;
2.遍历所有可能的木棒长度len,并得到木棒的个数cnt;
len满足 sum%len==0;
cnt=sum/len;
3.得出DFS的终止条件:
拼接成功的木棒 > cnt 或者 无法继续拼接
4 .然后就是这题的精华:各种剪枝与优化
1.木棍应从长到小遍历;
2.限制拼接一根木棒的木棍长度是递减的,因为 y+x == x+y;(但每次拼接一根木棒时都要从1 ~ n遍历一遍)
3.记录最近一个拼接的木棍长度,如果搜索失败,以后不再向当前木棒(stick)中添加相同长度的木棍;
4.如果当前木棍作为最后一部分使得木棒拼接成功(cab+num[i]==len),并且接下来拼接之后木棒的分支(DFS(stick+1,0,1)==false),那么就可以判断当前分支失败,直接回溯,即不再向当前木棒中添加相同长度的木棍。
总结
(吐槽一句:POJ不支持万能头文件~~)
1.DFS的剪枝是一个很重要的方面,也是很复杂的;可以先把整体的搜索框架码出来,再进行剪枝处理;
2.第4点是最难理解的,需要自己敲一遍才能理解;
代码展示
//#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<string.h>
#include<algorithm>
using namespace std;
/*
DFS+剪枝
遍历所有木棒的可能长度,直到符合条件
*/
int num[70];
bool vis[70];
int n,cnt,len;
bool DFS(int stick,int cab,int last){
if(stick>cnt)return true;
if(cab==len)return DFS(stick+1,0,1);
int fail=0;//剪枝: 避免访问多次相同长度的木条
for(int i=last;i<=n;i++){
if(!vis[i]&&cab+num[i]<=len&&fail!=num[i]){
vis[i]=1;
fail=num[i];
if(DFS(stick,cab+num[i],i+1))return true;//为什么是 i+1
vis[i]=0;
if(cab==0||cab+num[i]==len)return false;
//剪枝:1.当一根木条不能作为木棒的第一块,那么其他的也不行
// 2.当用一根木条无法拼接成功木棒时,其他若干根也不行 (作为木棒的最后一块时)
}
}
return false;
}
int main(){
while(true){
memset(vis,0,sizeof(vis));
cin>>n;
int val=0;
if(n==0)break;
for(int i=1;i<=n;i++){
cin>>num[i];
val+=num[i];
}
sort(num+1,num+n+1);
reverse(num+1,num+1+n);
for(int i=num[1];i<=val;i++){
if(val%i)continue;
cnt=val/i;
len=i;//当前选择的木棒长度
if(DFS(1,0,1)){
cout<<len<<endl;//当前拼接第i根木棒,长度为j,选择的是dik个小木棒
break;
}
}
}
return 0;
}