初识动态规划——0 1背包问题的其他应用

按照上节我们已经知道了解决动态规划的基本思路

(本节默认你已经基本掌握01背包问题,若不知道可以看我上次的博客)

(此节仅仅用于自己记录学习笔记,若有错误还望指出提醒)

初识动态规划——0 1背包问题的其他应用_第1张图片

2.列出递推公式

动态规划(简称DP)是一种将复杂问题分解成很多子问题,并将子问题的求解结果存储起来避免重复求解的一种算法。动态规划一般用来解决最优问题。

按照动态规划五部曲就是:1.了解dp数组的含义

3.dp数组初始化

4.遍历顺序

5.打印dp数组(用于检查是否有错误,一般省略)

这节主要记录关于动态规划0 1背包问题的隐藏题目(1.分割等和子集,2.目标和:给定一串数字,可以在数字前面加正号和负号使其和为你想一个目标值)

有时候你会用动态规划解决01背包问题,但你可能看到题目还不知道这就是01背包问题,

比如下面的题目:

1.神奇的分组(oj)

题目描述

现在给你一堆正整数,请你将其分为2组,要求2组的和相差最小。

例如:1 2 3 4 5,将1 2 4分为1组,3 5分为1组,两组和相差1,是所有方案中相差最少的。

输入
单组输入,每次输入两行
第1行:一个数N,N为正整数的数量。
第2 - N+1行,N个正整数。
(N <= 100, 所有正整数的和 <= 10000)

输出

输出这个最小差

样例输入 
5
1 2 3 4 5
样例输出 
1

 刚看题目是不是不知道这其实就是一个01背包问题?请看思路

初识动态规划——0 1背包问题的其他应用_第2张图片

 解题思路

首先题目说要求分组后的差值,其实可以把他抽象成一种01背包问题比如上题数组元素

[1,2,3,4,5]可以看成物品的大小为1,2,3,4,5;既然求差值最小那背包的容量就是所有物品和的一半,怎么理解?你想,你已经尽最大可能达到和的一半,那你剩下的数的和减去尽最大可能达到和的一半,那不就是最小差值了?那问题不就是转换成为01背包问题了?

show me code!

//神奇的分组+--------------------------------------------+
#include
#include
long long a[111]={0}, n, i, s = 0, dp[11111]={0}, j, k;
//dp数组代表背包容量为其下标能存的最大容量
int max(int a, int b)//用来取最大值
{
	if (a >= b)
		return a;
	else
		return b;
}
int main()
{
	
	scanf("%d", &n);//输入n,然后输入n个数放入a数组
	for (i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		s += a[i];//求出所有元素和
	}
	k = s;
	if (s % 2 != 0)//求出元素和的一半,代表背包容量
		s = s / 2 + 1;
	else
		s = s / 2;
	for (i = 1; i <= n; i++)//遍历物品,这里指数组里的每一个数
	{
		for (j = s; j >= a[i]; j--)//遍历背包容量,这里从数组和的一半开始代表背包容量
		{
				dp[j] = max(dp[j], dp[j - a[i]] + a[i]);
                //记录背包容量为j能装的最大物品容量的和
		}
	}
	k = abs(k - 2 * dp[s]);//记录差值
	printf("%lld\n", k);//打印差值
	return 0;
}

 2.目标和

题目描述

现在给你一堆正整数,你可以在某些数的前面加减号,使其所有数之和等于目标值,问有多少种放法使其达到目标值

例如:1 1 1 1 1,目标值为3,你可以在5个1中任意一个1前面加负号使所有数之和为3,所以有5种方法。

输入
每次输入3行
第1行:一个数N,N为正整数的数量。
第2 - N+1行,N个正整数。
第3,目标数
(N <= 100, 所有正整数的和 <= 10000)

输出

输出放法的次数

是不是和我一样看不出来这是一个01背包问题?

初识动态规划——0 1背包问题的其他应用_第3张图片

 解题思路

其实这题可以把不放负数的放一个集合里,放负数的放一个集合里

比如设left为加法集合元素的和,right为减法集合元素的和,sum为原来集合元素的和,target为目标数(此时还未放负号,只是把数先分好)

那很容易得到下面式子:

left + right = sum;

left - right = target;

因此:left = (sum + target) / 2;sum 和 target又是你要输入的所以可以求出left。

注意(如果sum + target不能整除2,代表不可能凑成target这个数,不信你可以列几个数试试,例如:把上面题给的target等于3改为等于4)

好,既然找到left,那又有什么用呢?回到题目本身,要求加负号后使数的和为目标值,那是不是就是找到数组里面的某些数放到left里面,求有多少种组合次数?left是不是就可以看成背包的容

量,求多少种放法?

代码如下:

#include
#include
int main()
{
	int n, i, j, sum=0,target,left;
	int  a[1000] = { 0 }, dp[1000] = { 0 };
	scanf("%d", &n);//输入n,然后输入n个数放入a数组
	for (i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		sum += a[i];//求出所有元素和
	}
	scanf("%d", &target);
	if ((sum + target) % 2 != 0)
	{
		printf("不能凑成目标数\n");
			return -1;
	}
	else
		left = (sum + target) / 2;
	dp[1] = 1;
	for (i = 1; i <= n; i++)//遍历物品,这里指数组里的每一个数
	{
		for (j = left; j >= a[i]; j--)//遍历背包容量,这里从数组和的一半开始代表背包容量
		{
				dp[j] += dp[j - a[i]];//记录凑成容量为j的方法
		}
	}
	printf("\n%d", dp[left]);
	return 0;
}

你可能感兴趣的:(动态规划,算法)