P1120 小木棍 [数据加强版]
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过5050。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
搜索+剪枝
先贴一个70分的代码:
#include<bits/stdc++.h> #define N 1010 using namespace std; int n,a[100],tot,sum,sh[N]; bool vis[N],flg,pvis[100]; void scz(int k){ for(int i=1;i<=k;i++) pvis[sh[i]]=1; } void dfs(int k,int l,int L){ if(l==L){ ++sum; if(sum*L==a[0]) flg=1; scz(k-1); return; } for(int i=1;i<=tot;i++){ if(!vis[i]&&l+a[i]<=L&&!pvis[i]){ vis[i]=1;sh[k]=i; dfs(k+1,l+a[i],L); vis[i]=0; } } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[++tot]); if(a[tot]>50) --tot; // a[0]+=a[tot]; a[n+1]=max(a[n+1],a[tot]); } for(int i=0;i<=tot;i++) a[0]+=a[i]; // ,printf("%d\n",a[i]); // printf("%d\n",a[0]); for(int i=a[n+1];i<=a[0];i++){ memset(pvis,0,sizeof(pvis)); if(a[0]%i==0){ sum=0; dfs(0,0,i); if(flg){ printf("%d\n",i); break; } } } return 0; }
剪枝大法好
#include<bits/stdc++.h> #define N 1010 using namespace std; int n,a[100],tot,sum,L,last; bool vis[N]; bool dfs(int R,int M){//还剩下的木棍,需要拼凑的木棍长度 if(R==0&&M==0) return true; if(M==0) M=L; int st=1; if(M!=L) st=last+1;//剪枝4,从下一根更短的进行尝试 ,而不是从头开始 for(int i=st;i<=tot;i++){ if(!vis[i]&&a[i]<=M){ if(i>1&&vis[i-1]==false&&a[i]==a[i-1]){ continue; }//剪枝1,如果上一个没被选中且当前这一个与上一个长度相等 vis[i]=1;last=i; if(dfs(R-1,M-a[i])) return true; else vis[i]=0; if(M==L||a[i]==M) return false;//剪枝2,如果第一根木棍不能更新,那么就永远不能更新 //剪枝3 如果当前木棍在==M的情况下不能成功,那就不可能成功 } } return false; } bool cmp(int x,int y) { return x>y; } int main() { scanf("%d",&n); for(int i=1; i<=n; i++) { scanf("%d",&a[++tot]); if(a[tot]>50) --tot; a[n+1]=max(a[n+1],a[tot]); } for(int i=0; i<=tot; i++) a[0]+=a[i]; sort(a+1,a+1+tot,cmp); for( L=a[n+1]; L<=a[0]/2; L++) { memset(vis,0,sizeof(vis)); if(a[0]%L) continue; if(dfs(tot,L)){ printf("%d\n",L); break; } } if(L>a[0]/2) printf("%d\n",a[0]); return 0; }
剪枝方法总结:
1) 要选择合适的搜索顺序 如果一个任务分为 A, B, C…..等步骤(先后次序无关), 要优先尝试可能性少的步骤
2) 要发现表面上不同,实质相同的重复状态,避免重复的搜索
3) 要根据实际问题发掘剪枝方案