poj1011Stick(dfs+剪枝)
题目链接:http://poj.org/problem?id=1011
Dfs中状态的转移是重点
1.分析问题
问题:将n个部分拼接成sum/h个长度为h的木棍。部分分别为stk[i]
2.分析解状态
r=sum/h
因为h是固定的,不作为状态
初始状态:
取了0个部分。剩n个部分,正在拼的木棍有h还未拼完,剩r个木棍要拼
取一个部分,还剩n-1个部分,正在拼的木棍还剩h-stk[i]还未拼完,还剩r个木棍要拼
设正在拼的木棍还剩hr
取k个部分,拼完一根木棍,还剩n-k个部分,正在拼的木棍还有0还未拼完,还hr-1个木棍要拼
设剩下rs木棍要拼,还剩nr个部分
解状态:
拼完了r根木棍,还剩0个部分,正在拼的木棍有0还未拼完,还剩0个木棍要拼
3.写出状态转移dfs(剩n-k个部分,还有hr个部分要拼,还剩rs个木棍要拼)
dfs(n,h,r)->dfs(nr,hr,rs)->dfs(0,0,0) (hr>stk[next])
dfs(nr,hr,rs)表示在该状态时,能否拼成功,返回值为bool型
当nr==0&&hr==0&&rs==0
则返回true
当nr!=0&&hr==0&&rs!=0
此时说明拼完了一根木棍,
return dfs(nr-1,h,rs-1)
当nr!=0&&hr!=0&&rs!=0
for循环选择了一个部分i以后
return dfs(nr-1,hr-stk[i],rs)
#include<iostream> #include<vector> #include<algorithm> #include<cstring> using namespace std; /* 对木棍的长度h有三个限制 1:所有parts的最大长度maxL<=h 且h<=parts长度和sum 2:h|sum 3:h<=50 显然所有的限制之中最重要的是找到sum的因子 因子还要大于等于maxL且小于等于50(暂时先不管这个) */ const int maxn=65; int n,sum,maxL; int stk[maxn]; int h[maxn]; bool vis[maxn]; bool cmp(int a,int b){ return a>b; } /*剪枝有四 1: 如果选择前面的部分之后不成功(vis[i-1]==0),而当前项又与前面项相同(stk[i]==stk[i-1]),则说明该项不可能,跳过(前提是i!=1) 2: 如果部分i是一根木棒的第一块,那么若i不可取,不需要去遍历其他的数来替换部分i,可直接跳过 3: 如果用部分i去填补一根木棍,正好使其拼接完成,但是dfs表明选择部分i之后不能拼接, 则不必找其他部分来替换,直接将失败反馈给前一个部分的选择 4: 一根木棍从前到后的每一块都要保证长度从大到小,即当前选择的块不能比前一块大 5: 如果只剩一根木棍还没拼,那肯定成功 */ bool dfs(int nr,int hr,int rs,int h){//dfs(剩下的部分数,木棍剩下未拼接的长度hr, 剩下还没拼的木棍数rs,木棍的长度h) if(nr==0&&hr==0&&rs==0) return true; else if(nr!=0&&hr==0 )//说明已经拼接完一个木棍了,接下来要拼接新的 { hr=h; rs--; } else if (rs==1) return true; //都没有的话,说明正在选择可取部分来填充木棍剩余的长度 for(int i=1;i<=n;i++) { if(!vis[i]&&hr>=stk[i])//判断是否可取 { vis[i]=1; if(i!=1&&!vis[i-1]&&vis[i-1]==vis[i]) continue; if(dfs(nr-1,hr-stk[i],rs,h))//如果取这个部分可以成功 return true; else//否则,部分i不可取 { vis[i]=0; if(hr==stk[i]||hr==h)//拼接完一个木棍之后,选择下一个木棍的第一块,若第一块不可取,则直接退出,去修改上一个木棍 return false; } } }//遍历了所有可取部分,没有一个能成功的,则记得返回失败 return false; } int main (){ while(cin>>n) { if(!n) break; sum=maxL=0; for(int i=1;i<=n;i++) { cin>>stk[i]; if(maxL<stk[i]) maxL=stk[i]; sum+=stk[i]; } //找因子 memset(h,0,sizeof(h)); int cur=1; if(n==1)//注意考虑n==1的情况 { cout<<sum<<endl; continue; } else{ for(int i=1;i*i<=sum;i++) //h中包含小于maxL的因子 if(sum%i==0) h[cur++]=i; for(int i=cur-1;i>0;i--) h[cur++]=sum/h[i]; //h中的因子元素从小到大 } sort(stk+1,stk+n+1,cmp); //从大到小 for(int i=1;i<cur;i++) //遍历所有的因子 { memset(vis,0,sizeof(vis)); if(h[i]<maxL) continue; if(dfs(n,h[i],sum/h[i],h[i]))//如果这个木棍长度可以被拼接 { cout<<h[i]<<endl; break; } }//一定有一个因子能够满足(sum自身) } return 0; }