动态规划--背包

背包分类:0-1背包完全背包分组背包多重背包


01背包(ZeroOnePack):有N件物品和一个容量为V的背包。每种物品均只有一件。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

完全背包(CompletePack):有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

多重背包(MultiplePack): 有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

*************************************************************************************************************

先来分析01背包:
01背包(ZeroOnePack): 有N件物品和一个容量为V的背包。(每种物品均只有一件)第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:
         f[i][v]=max{ f[i-1][v] , f[i-1][v-c[i]]+w[i] }
把这个过程理解下:在前i件物品放进容量v的背包时,
它有两种情况:
第一种是第i件不放进去,这时所得价值为:f[i-1][v]
第二种是第i件放进去,这时所得价值为:f[i-1][v-c[i]]+w[i]
(第二种是什么意思?就是如果第i件放进去,那么在容量v-c[i]里就要放进前i-1件物品)
最后比较第一种与第二种所得价值的大小,哪种相对大,f[i][v]的值就是哪种。

例:hdu 2955  Robberies:

给定一个被抓的概率p,还有一些银行,给定这些银行的钱数以及抢劫这些银行被抓的概率。
求在被抓的概率不大于p的情况下最多可以抢到多少钱。
dp[i]表示抢劫i元逃跑的概率,则
     dp[i] = max(dp[i], dp[i - v[j]] * (1 - w[i]));

抢劫多个银行逃跑的概率等于抢劫每个银行逃跑概率之积。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
double dp[10010];

struct node
{
	int v;
	double p;
}a[110];

int main()
{
	int t, n, sum;
	double p;
	cin >> t;
	while (t--)
	{
		cin >> p >> n;
		sum = 0;
		memset(dp, 0, sizeof(dp));
		for (int i = 0; i < n; i++)
		{
			cin >> a[i].v >> a[i].p;
			sum += a[i].v;
		}
		dp[0] = 1;

		for (int i = 0; i < n; i++)
		{
			for (int j = sum; j >= a[i].v; j--)
			{
				dp[j] = max(dp[j], dp[j - a[i].v] * (1 - a[i].p));
			}
		}

		for (int i = sum; i >= 0; i--)
		{
			if (dp[i] > (1 - p))
			{
				cout << i << endl;
				break;
			}
		}
	}

	return 0;
}
*************************************************************************************************************

完全背包:
完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
完全背包按其思路仍然可以用一个二维数组来写出:
         f[i][v]=max{ f[i][v] , f[i-1][v-k*c[i]] + k*w[i]   |  0<=k*c[i]<=v}

例:hdu 2159 FATE 完全背包

Input
输入数据有多组,对于每组数据第一行输入n,m,k,s(0 < n,m,k,s < 100)四个正整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下来输入k行数据。每行数据输入两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪xhd会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)
Output
输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。

           dp[i][j]表示花费i忍耐度杀j个怪可获得的最大经验值
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
int dp[105][105];

int main()
{
	int n, m, k, s, a, b;
	while (cin >> n >> m >> k >> s)
	{
		int num = inf;
		memset(dp, 0, sizeof(dp));

		for (int i = 0; i < k; i++)  //k种怪
		{
			cin >> a >> b;      //收益-代价
			for (int j = b; j <= m; j++)     //消耗(代价)容忍度 b->m
			{
				for (int d = 0; d <= s; d++)  //打怪数目
				{
					dp[j][d] = max(dp[j][d], dp[j - b][d - 1] + a);
					if (dp[j][d] >= n)
					{
						num = min(num, j);
					}
				}

			}
		}
		if (num <= m)cout << m - num << endl;
		else cout << "-1" << endl;
	}

	return 0;
}
*************************************************************************************************************

多重背包

多重背包(MultiplePack): 有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:

        f[i][v]=max{ f[i-1][v-k*c[i]] + k*w[i]  |  0<=k<=n[i]}


例:hdu 2844 Coins 多重背包
题意:Tony想要买一个东西,他只有n中硬币每种硬币的面值为a[i]每种硬币的数量为c[i]要买的物品价值不超过m。


输入:第一行输入n和m,第二行输入n个硬币的面值和n个硬币的数量,输入0 0结束
输出:1到m之间有多少价格Tony可以支付
            dp[i]表示背包中重量为i时所包含的最大价值
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int dp[100010], a[110], c[110];
int n, m;

//m背包的总容量、v物品的体积、w物品的价值
void OneZeroPack(int m, int v, int w)  //0-1背包
{
	for (int i = m; i >= v; i--)
		dp[i] = max(dp[i], dp[i - v] + w);
}

//m背包的总容量、v物品的体积、w物品的价值
void CompletePack(int m, int v, int w)  //完全背包
{
	for (int i = v; i <= m; i++)
		dp[i] = max(dp[i], dp[i - v] + w);
}

//m背包的总容量、v物品的体积、w物品的价值、num物品的数量
void MultiplePack(int m, int v, int w, int num)//多重背包
{
	if (v*num >= m)    //此时相当于物品数量无限进行完全背包
	{
		CompletePack(m, v, w);
		return;
	}
	int k = 1;        //否则进行01背包转化,具体由代码下数学定理可得
	for (k = 1; k <= num; k <<= 1)
	{
		OneZeroPack(m, k*v, k*w);
		num = num - k;
	}
	if (num)
		OneZeroPack(m, num*v, num*w);
}

int main()
{
	while (cin >> n >> m)
	{
		if (n == 0 && m == 0)  break;
		for (int i = 0; i<n; i++)  cin >> a[i];
		for (int i = 0; i<n; i++)  cin >> c[i];
		for (int i = 0; i <= m; i++)   dp[i] = -INF;
		dp[0] = 0;
	
		for (int i = 0; i<n; i++)
		{
			MultiplePack(m, a[i], a[i], c[i]);
		}
		int sum = 0;
		for (int i = 1; i <= m; i++)
			if (dp[i]>0)    sum++;
		cout << sum << endl;
	}
	return 0;
}

定理:一个正整数n可以被分解成1,2,4,…,2^(k-1),n-2^k+1(k是满足n-2^k+1>0的最大整数)的形式,且1~n之内的所有整数均可以唯一表示成1,2,4,…,2^(k-1),n-2^k+1中某几个数的和的形式。

证明如下:
(1) 数列1,2,4,…,2^(k-1),n-2^k+1中所有元素的和为n,所以若干元素的和的范围为:[1, n];
(2)如果正整数t<= 2^k – 1,则t一定能用1,2,4,…,2^(k-1)中某几个数的和表示,这个很容易证明:我们把t的二进制表示写出来,很明显,t可以表示成n=a0*2^0+a1*2^1+…+ak*2^(k-1),其中ak=0或者1,表示t的第ak位二进制数为0或者1.
(3)如果t>=2^k,设s=n-2^k+1,则t-s<=2^k-1,因而t-s可以表示成1,2,4,…,2^(k-1)中某几个数的和的形式,进而t可以表示成1,2,4,…,2^(k-1),s中某几个数的和(加数中一定含有s)的形式。
(证毕!)


推荐:

woj 1537 A Stone-I  转化成背包

woj 1538 B Stone-II 转化成背包

poj 1170 Shopping Offers 状压+背包

zoj 3769 Diablo III 带限制条件的背包

zoj 3638 Fruit Ninja 背包的转化成组合数学

hdu 3092 Least common multiple 转化成完全背包问题

poj 1015 Jury Compromise 扩大区间+输出路径

poj 1112 Team Them UP 图论+背包


End


猜你喜欢

转载自blog.csdn.net/qq_34777600/article/details/79677732