题意: n个整数(n<=14),要求将这n个整数分成若干个部分,使得尽可能多的部分的数,可以通过加减法计算出S(0<=S<=10^8)。
输入: 第一行是T,代表测试数量。 接下来有T组数据,每组的第一行是n,第二行是n个数(0<=a[i]<=10^7)。
输出: 对于每一组数据,输出最多可以分成多少个可以计算出S的部分。
样例:
Sample Input
2
5 5 1 2 3 4 5 5 5 1 2 3 8 8
Sample Output
3
2 |
思路:
动态规划解法见这篇文章: http://blog.csdn.net/u014664226/article/details/51107676
我这里就讲讲暴力搜索的解法,n最大也才14,可以直接暴力搜索。
第一步:对于每一个子集(2^14个),计算出这个子集中的数能否计算出S,每个子集用14位的二进制数来表示。
第二步:按照最低的二进制位,对所有可以计算出S的子集进行分类,然后直接暴力搜索所有的可能性。
本题难度不大,主要是n太小了,导致暴力就可以做出来。值得一提的是F函数,F函数多次调用所需
的临时空间在外部已经申请好了(即D数组),代码中直接使用,避免了在F的内部调用的时候进行频繁地申请和释放。
计算出所有的Subset数组之后,需要按照最低位的1来分组,这时使用了指针来复用D数组的空间。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <ctime>
#include <map>
#include <list>
#include <cstring>
#include <algorithm>
using namespace std;
int T;//T组数据
int N;//N个数
int S;//和为S
int A[14];//N个数的值
int D[1<<15];//临时空间
int Subset[1<<14];//子集中的数能否计算出S
int *aLow[14],nLow[14];//最低位为i的子集,存在于数组aLow[i]中,长度为nLow[i]。
void F(int i,int L,int R,int mask){
//mask是14位的整数,表示目前集合的状态
//此时,i表示本次调用正在考虑是否往集合中加入A[i]
//D数组中,下标为[L,R)的区间中存的是不加入A[i]的所有可能的计算结果
if(i==N) return;
//如果不加 A[i],增加i,然后继续调用F
F(i+1,L,R,mask);
//如果加入A[i],计算加入A[i]之后的所有可能的计算结果,存入D数组下标[2*L,2*R)
for(int t=L;t<R;++t){
D[t<<1]=D[t]+A[i];
D[t<<1|1]=D[t]-A[i];
//如果计算出S,提前结束(这个判断是为了提高效率,去掉不影响正确性)
if(D[t<<1]==S || D[t<<1|1]==S){
Subset[mask|(1<<i)]=1;
return;
}
}
//继续递归调用F,传入正确的参数
F(i+1,L << 1,R << 1,mask|(1<<i));
}
int G(int i,int mask,int vn){
//mask表示目前14个数的使用状态
//i表示目前考虑到下标为i的数
//vn是目前有多少个集合可以计算出S
if(i==N) return vn;
//如果不加第i个数
int ANS=G(i+1,mask,vn);
//考虑加上一个最低位为i的集合,然后继续递归求答案
if(mask&(1<<i));
else {//如果mask的位i不为1,则遍历所有最低位为i的子集
for(int t=0;t<nLow[i];++t){
//如果该子集与mask相容,就递归计算答案,并与ANS比较。
if(aLow[i][t]&mask) ;
else ANS=max(ANS,G(i+1,mask|aLow[i][t],vn+1));
}
}
//返回最终的答案
return ANS;
}
int main()
{
scanf("%d",&T);
for(int Ti=0;Ti<T;++Ti){
//输入数据
scanf("%d%d",&N,&S);
for(int i=0;i<N;++i) scanf("%d",&A[i]);
//第一部分:计算Subset数组
memset(Subset,0,sizeof(Subset));
D[1]=0;
F(0,1,2,0);
//中间部分:将Subset数组按照最低位的1的位置进行分类
//此处复用了D数组的空间,aLow[i][0]~aLow[i][nLow[i]-1]存了所有最低位为i的集合。
for(int i=0;i<N;++i) aLow[i]=&D[1<<(N-i-1)],nLow[i]=0;
for(int i=0;i<(1<<N);++i) if(Subset[i]){
for(int j=0;j<N;++j){
if((i>>j)&1){
aLow[j][nLow[j]++]=i;
break;
}
}
}
//计算答案
int ANS=G(0,0,0);
printf("%d\n",ANS);
}
return 0;
}