POJ 1011 Sticks dfs,剪枝 难度:2

时间:2021-06-30 17:21:57

http://poj.org/problem?id=1011

要把所给的集合分成几个集合,每个集合相加之和ans相等,且ans最小,因为这个和ans只在[1,64*50]内,所以可以用dfs一试

首先ans需要满足两个条件1.可以被总集合的和sum整除 2.是总集合的某个子集的和 对于条件1,可以通过试除遍历 对于条件2,可以通过dp预筛选,这两个花费时间都不大

接着搜索能不能划分成集合之和恰为ans的若干集合,

1. 可以从大向小找,因为大的更不灵活,而且所有的元素都需要取到

2.比如对于5,4,2,2,1 使用第一个2,向下找,没有找到答案,不用第一个2之后,也不需要再找第二,第三个2了

如果之前找过相同的数字了,那么之后就不用再找相同的数字

3.如果这次数字刚好组合成为一个ans,那么能不能组成由剩下的数字决定,也就是已经得到了一个新集合和为ans了,剩下的能够组成若干和为ans的集合则可以取ans,否则不能取ans

#include <cstdio>
#include<cstring>
#include <algorithm>
using namespace std;
int a[64],n;
bool cmp(int a,int b){return a>b;}
bool reachable[64*51];
bool used[64];
bool dfs(int s,int tmp,int sub){
// printf("s:%d tmp:%d sub:%d\n",s,tmp,sub);
used[s]=true;
tmp+=a[s];
if(tmp>sub){// 不可能出现
puts("ERROR");
used[s]=false;
return false;
}
if(tmp==sub){//恰好成为一个和为ans的集合
for(int i=0;i<n;i++){
if(!used[i]){
if(dfs(i,0,sub))return true;//dfs新的集合,直接返回剩下的元素能不能组合成若干和为ans的集合
else {
used[s]=false;
return false;
}
}
}
return true;//所有元素都被使用了
}
int f=-1;
for(int i=s+1;i<n;i++){
if(f==a[i]||used[i]){continue;}//如果这个状态的这个函数已经找过相同的数
if(tmp+a[i]==sub){//直接返回剩下的元素的组合状态
if(dfs(i,tmp,sub))return true;
else {
used[s]=false;
return false;
}
}
else if(tmp+a[i]<sub){//可以试着添加进这个集合
if(dfs(i,tmp,sub))return true;
}
f=a[i];
}
used[s]=false;
return false;
}
int main(){
while(scanf("%d",&n)==1&&n){
int sum=0,mx=0;
memset(reachable,false,sizeof(reachable));
for(int i=0;i<n;i++){
scanf("%d",a+i);
sum+=a[i];
mx=max(a[i],mx);
}
reachable[a[n-1]]=true;
reachable[0]=true;
sort(a,a+n,cmp);//从大到小搜索
for(int i=n-2;i>=0;i--){
for(int j=0;j<sum&&j+a[i]<=sum;j++){
if(reachable[j]){
reachable[j+a[i]]=true;//reachable[i]=true i可以被总集合中的某些元素加和得到
}
}
}
bool fnd=false;
for(int i=mx;i<sum;i++){
if(sum%i==0&&reachable[i]){
memset(used,0,sizeof(used));
if(dfs(0,0,i)){
printf("%d\n",i);
fnd=true;
break;
}
}
}
if(!fnd)printf("%d\n",sum);
} return 0;
}