【DFS】背包问题 | 从N个整数中选择K个数

背包问题

有n件物品,每件物品的重量为w[i],价值为c[i]。现在需要选出若干件物品放入一个容量为V的背包中,使得在选入背包的物品重量和不超过容量V的前提下,让背包中物品的价格之和最大,求最大价值。(1<=n<=20)

输入数据:

5 8 
3 5 1 2 2
4 5 2 1 3

//5件物品,背包容量为8
//重量分别为3 5 1 2 2
//价值分别为4 5 2 1 3

输出数据:

10

--------------------------------------我是题目和解题的分割线--------------------------------------

深度优先搜索——枚举所有完整路径以遍历所有情况。碰到死胡同才回退到最近的岔道口选择另一条岔路。

在这个问题中,对每件物品都有选和不选两种选择,这就是岔道口。而求最大价值需遍历全部可能性,2^5 = 32。

用的是递归,和【递归】Fibonacci数列 | 全排列 | n皇后问题 的思路差不多,只是要加上“岔道口”。

#include<cstdio>

int n,v,wei[100],val[100];
int maxV = 0;

//index为当前处理的物品编号
//sumW和sumV是当前总质量和总价值 
void dfs(int index,int sumW,int sumV)
{
	//已经把n件物品处理完毕(下标0-n-1),到达死胡同 
	if(index==n) return;
	//岔道口 
	dfs(index+1,sumW,sumV); //不选择第index件物品 
	//只有加入第index件物品后未超过容量V才能选择index件物品 
	if(wei[index]+sumW<=v)
	{
		//更新最大价值 
		if(val[index]+sumV>maxV) maxV = val[index]+sumV;
		dfs(index+1,sumW+wei[index],sumV+val[index]); //选择第index件物品 
	}
}

int main()
{
	int i;
	scanf("%d%d",&n,&v);
	for(i=0;i<n;i++)
		scanf("%d",&wei[i]);
	for(i=0;i<n;i++)
		scanf("%d",&val[i]);
	dfs(0,0,0);
	printf("%d\n",maxV);
	return 0;
}

 从N个整数中选择K个数

给定N个整数(可能有负数),从中选择K个数,使得这K个数之和恰好等于一个给定的整数X。如果有多种方案,选择它们中元素平方和最大的一个。数据保证这样的方案唯一。输出此方案。

输入数据:

4 2 6
2 3 3 4

//从4个整数2 3 3 4中选择2个数,使它们的和为6,

输出数据:

2 4

-------------------------------------这是题目和解题的分割线-------------------------------------

这道题和背包的思路差不多,但需要解决一下如何保存最优方案的问题。

首先,需要一个数组tmp,用以存放当前已选择的整数。这样当试图进入”选index号数“这条分支时,就把a[index]加入到tmp中;而当这条分支结束时,就把它从tmp中去除,使它不会影响”不选index号数“这条分支。接着,如果在某个时候发现当前已经选择了K个数,且这K个数之和恰好为x时,就去判断平方和是否比已有的最大平方和还要大,如果是,则更新最大值,并把tmp赋给用以存放最优方案的数组out。

#include<cstdio>
#include<vector>

using namespace std;

//平方和可能为0,所以初始值为-1 
int n,a[100],x,s,maxSumSqu = -1;
vector<int> tmp,out;

void dfs(int index,int nowX,int sum,int sumSqu)
{
	//当满足题目条件 
	if(nowX==x&&sum==s)
	{
		if(sumSqu>maxSumSqu)
		{
			maxSumSqu = sumSqu; //更新最大平方和 
			out = tmp; //更新最优方案 
		}
		return;
	}
	//这个判断不能放在第一步,因为index==n就return可能会错过最佳方案的更新 
	if(index==n||nowX>x||sum>s) return;
	//选index号数 
	tmp.push_back(a[index]); //记录当前数字a[index] 
	dfs(index+1,nowX+1,sum+a[index],sumSqu+a[index]*a[index]);
	//不选index号数 
	tmp.pop_back(); //删除数字a[index] 
	dfs(index+1,nowX,sum,sumSqu);
}

int main()
{
	int i;
	scanf("%d%d%d",&n,&x,&s);
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);
	dfs(0,0,0,0);
	for(i=0;i<out.size();i++)
		printf("%d ",out[i]);
	return 0;
}

如果稍微修改一下题目,假设N个整数中的每一个都可以被选择多次,那么选择K个数,使得K个数之和恰好为X。

那么只需要将选择index号数的dfs(index+1,nowX+1,sum+a[index],sumSqu+a[index]*a[index]); 语句修改为dfs(index,nowX+1,sum+a[index],sumSqu+a[index]*a[index]); 不进入index+1,依旧进入index。

测试数据:三个整数1、4、7,需要从中选择5个数,使得这5个数之和为17。结果是3个1和2个7。


摘自《算法笔记》胡凡、曾磊著

展开阅读全文

没有更多推荐了,返回首页