WOJ 1608 Calculation (暴力搜索/动态规划)

题意:

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



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值