Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 152214 | Accepted: 36272 |
Description
Input
Output
Sample Input
9 5 2 1 5 2 1 5 2 1 4 1 2 3 4 0
Sample Output
6 5
题意:给出n根小棒的长度stick[i],已知这n根小棒原本由若干根长度相同的长木棒(原棒)分解而来。求出原棒的最小可能长度。
思路:dfs+剪枝。蛮经典的题目,重点在于dfs剪枝的设计。先说先具体的实现:求出总长度sumlen和小棒最长的长度max,则原棒可能的长度必在max~sumlen之间,然后从小到大枚举max~sumlen之间能被sumlen整除的长度len,用dfs求出所有的小棒能否拼凑成这个长度,如果可以,第一个len就是答案。
下面就是关键的了,就是这道题dfs的实现和剪枝的设计:
1.以一个小棒为开头,用dfs看看能否把这个小棒拼凑成len长,如果可以,用vis[i]记录下用过的小棒,然后继续以另外一个小棒为开头,以此类推。
2.小棒的长度从大到小排序,这个就不解释了。
3.如果当前最长的小棒不能拼成len长,那么就返回前一步,更改前一步的最长小棒的组合情况(这里不能是全部退出),不用再继续搜索下去了。
4.最重要的,就是比如说17,9,9,9,9,8,8,5,2……如果当前最长小棒为17,它与第一个9组合之后dfs发现不能拼成len,那么17就不用和后面所有的9组合了,而直接和8开始组合。这个剪枝直接从TLE到16MS,很强大。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int n,len,stick[70];
bool flag,visit[70];
bool cmp(int a,int b)
{
return a>b;
}
void dfs(int num,int clen,int cur)//num为当前已被用过的小棒数,cur为当前要处理的小棒
{
if(flag) return ;
if(clen==0)//当前长度为0,寻找下一个当前最长小棒
{
int k=0;
while(visit[k]) k++;//寻找当前第一个最长小棒
visit[k]=true;
dfs(num+1,stick[k],k+1);
visit[k]=false;
return ;
}
if(clen==len)//当前长度为len,即又拼凑成了一根
{
if(num==n) flag=true;//完成的标志 :所有的n根小棒都被用过了
else dfs(num,0,0);
return ;
}
for(int i=cur;i<n;i++)
{
if(!visit[i]&&clen+stick[i]<=len)
{
if(!visit[i-1]&&stick[i]==stick[i-1]) continue;
//最最最最重要的剪枝!!!!!不重复搜索
visit[i]=true;
dfs(num+1,clen+stick[i],i+1);
visit[i]=false;
}
}
}
int main()
{
while(cin>>n&&n)
{
int lensum=0;
flag=false;
for(int i=0;i<n;i++)
{
cin>>stick[i];
lensum+=stick[i];
}
sort(stick,stick+n,cmp);//从大到小排序
for(len=stick[0];len<lensum;len++)
{
if(lensum%len==0)//枚举能被sum整除的长度
{
memset(visit,0,sizeof(visit));
dfs(0,0,0);
if(flag) break;
}
}
cout<<len<<endl;
}
}