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; }