背包问题
先聊聊区分 贪心 和 01背包
背包问题:
有多个物品,重量不同、价值不同,以及一个容量有限的背包,选择一些物品装到背包中,问怎么裝才能使装进背包的物品总价值最大。根据不同的限定条件,可以把背包问题分为很多种,常见的有下面两种:
(1)如果每个物体可以切分。称为一般背包问题,用贪心法求最优解。比如吃自助餐,在饭量一定的情况下,怎么吃才能使吃到肚子里的最值钱?显然应该从最贵的食物吃起,吃完了最贵的再吃第2贵的,这就是贪心法。
(2) 如果每个物体不可分割,称为 0/1 背包问题。仍以吃自助餐为例,这次食物都是一份份的,每一份必须吃完。如果最贵的食物一份就超过了你的饭量,那只好放弃。这种问题无法用贪心法求最优解。
01背包问题
01背包是一种动态规划问题。动态规划的核心就是状态转移方程,解释01背包状态转移方程的原理。
01背包问题可描述为如下问题:
有一个容量为V的背包,还有n个物体。现在忽略物体实际几何形状,我们认为只要背包的剩余容量大于等于物体体积,那就可以装进背包里。每个物体都有两个属性,即体积w和价值v。
问:如何向背包装物体才能使背包中物体的总价值最大?

例题:骨头收集器
问题描述
许多年前,在泰迪的家乡,有一个人叫“骨头收集者”。这个人喜欢收集各种骨头,比如狗的、牛的,他也去坟墓......
骨头采集者有一个体积为V的大袋子,在他收集的旅途中有很多骨头,显然,不同的骨头有不同的价值和不同的体积,现在给定每块骨头沿途的价值,你能计算出骨头收集者可以得到的总价值的最大值吗?

输入
第一行包含一个整数 T ,即案例数。
后面是T个案例,每个案例三行,第一行包含两个整数N,V,(N <= 1000,V <= 1000)代表骨头的数量和他的袋子的体积。第二行包含 N 个整数,表示每个骨骼的值。第三行包含 N 个整数,表示每块骨头的体积。
输出
每行一个整数,表示总值的最大值(此数字将小于 231)。
示例输入
1
5 10
1 2 3 4 5
5 4 3 2 1
示例输出
14
原题链接:Problem - 2602 (hdu.edu.cn)
代码(二维形式):
(二维形式)但过不了这道题!但用结构体能过!
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
//#define int long long
const int N = 1010;
int v[N], w[N];
//int f[N];
int f[N][N];
int n, m;
int x, y;
int main()
{
	int t;
	cin >> t;
	while (t--) {
		memset(f, 0, sizeof f);
		memset(v, 0, sizeof v);
		memset(w, 0, sizeof w);
		cin >> n >> m;
		for (int i = 1; i <= n; i++)
			cin >> w[i];
		for (int i = 1; i <= n; i++)
			cin >> v[i];
		
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				f[i][j] = f[i - 1][j];
				if (j >= v[i]) {
					f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
				}
			}
		}
		cout << f[n][m] << endl;
		
	}
	return 0;
}
一维优化(滚动数组):
通过观察,二维f[][] [] [],每一行是从上面一行算出来的,只跟上面一行有关系,跟更前面的行没有关系。用新的一行覆盖原来的一行就可以了,所以可以把i层去掉
优化后,空间复杂度从O(NV)->O(V)。
缺点:覆盖了中间转移状态,只留下了最后的状态,所以损失了很多信息,导致无法输出背包的方案。
一维形式:能过!
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
//#define int long long
const int N = 1010;
int v[N], w[N];
int f[N];
int n, m;
int x, y;
int main()
{
	int t;
	cin >> t;
	while (t--) {
		memset(f, 0, sizeof f);
		memset(v, 0, sizeof v);
		memset(w, 0, sizeof w);
		cin >> n >> m;
		for (int i = 1; i <= n; i++)
			cin >> w[i];
		for (int i = 1; i <= n; i++)
			cin >> v[i];
		for (int i = 1; i <= n; i++)
			for (int j = m; j >= v[i]; j--)//倒过来
			{
                //f[i][j] = f[i - 1][j];i层去掉,恒等式了,删去
				f[j] = max(f[j], f[j - v[i]] + w[i]);
			}
		cout << f[m] << endl;
	}
	return 0;
}
例题:最大报销额
原题:Problem - 1864 (hdu.edu.cn)
Problem Description
现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。
Input
测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(<=30)是发票张数。随后是 N 行输入,每行的格式为:
m Type_1:price_1 Type_2:price_2 ... Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
Output
对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。
Sample Input
200.00 3
2 A:23.50 B:100.00
1 C:650.00
3 A:59.99 A:120.00 X:10.00
1200.00 2
2 B:600.00 A:400.00
1 C:200.50
1200.50 3
2 B:600.00 A:400.00
1 C:200.50
1 A:100.00
100.00 0
Sample Output
123.50
1000.00
1200.50
代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int v[100], w[100];//这道题体积与价值等同
double dp[3000005];
int main()
{
    double q;
    int n, t;
    while (scanf("%lf%d", &q, &n) != EOF)
    {
        double price;
        char ch;
        int j = 0, k = 0;
        if (n == 0)
        {
            break;
        }
        for (int i = 0; i < n; i++)
        {
            scanf("%d", &t);
            int flag = 1;
            double a = 0, b = 0, c = 0;
            while (t--)
            {
                getchar();
                scanf("%c:%lf", &ch, &price);
                if (ch == 'A')//算出每个发票的类型A物品的总价格
                    a += price;
                else if (ch == 'B')//算出每个发票的类型B物品的总价格
                    b += price;
                else if (ch == 'C')//算出每个发票的类型C物品的总价格
                    c += price;
                else//如果出现其他类型的物品就把flag标记为0,代表为不能报销的发票
                    flag = 0;
                if (a > 600 || b > 600 || c > 600)//如果A或者B或者C物品的单个价格有一个大于600就说明这个发票不能报销,就标记为0
                    flag = 0;
            }
            if (flag == 1 && (a + b + c) <= 1000)//当flag等于1并且A,B,C的总钱数小于等于1000就是一张可以报销的发票
            {
                v[k] = (a + b + c) * 100;//因为题目说了最后的结果是保留两位小数,我们就把所以数都转换为整形来使得计算更加简单,所以就乘以100.
                w[k] = (a + b + c) * 100;//同上一样的作业
                k++;//存储完之后加1,接下来再存储满足条件可报销的情况
            }
        }
        int s = q * 100;//这时候总钱数也需要乘以100.
        memset(dp, 0, sizeof(dp));
        for (int i = 0; i < k; i++)
        {
            for (int j = s; j >= v[i]; j--)
            {
                dp[j] = max(dp[j], dp[j - v[i]] + w[i]);//又回到了01背包的问题
            }
        }
        printf("%.2lf\n", dp[s] / 100);//因为之前的总数和每个发票的价格都乘了100,所以最后我们需要再把那个100除回来,所以输出的是DP[C]/100.
    }
}
例题:
FATE
Problem Description
最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务。久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级。现在的问题是,xhd升掉最后一级还需n的经验值,xhd还留有m的忍耐度,每杀一个怪xhd会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到0或者0以下时,xhd就不会玩这游戏。xhd还说了他最多只杀s只怪。请问他能升掉这最后一级吗?
Input
输入数据有多组,对于每组数据第一行输入n,m,k,s(0 < n,m,k,s < 100)四个正整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下来输入k行数据。每行数据输入两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪xhd会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)
Output
输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。
Sample Input
10 10 1 10
1 1
10 10 1 9
1 1
9 10 2 10
1 1
2 2
Sample Output
0
-1
1
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
int v[110], w[110];
int f[110][110];
int main()
{
    ios::sync_with_stdio(false); // 关闭输入输出流的同步,提高输入输出效率
    int n, m, k, s; // 定义变量 n, m, k, s
    int ans = 0x3f3f3f3f; // 初始化 ans 为一个很大的数值
    while (cin >> n >> m >> k >> s) { // 循环读取 n, m, k, s 的值
        memset(f, 0, sizeof f); // 初始化数组 f 为0
        ans = 0x3f3f3f3f; // 重新初始化 ans 为一个很大的数值
        for (int i = 1; i <= k; i++) // 循环读取数组 w 和 v 的值
            cin >> w[i] >> v[i];
        for (int t = 1; t <= k; t++) { // 循环遍历每个物品
            for (int i = v[t]; i <= m; i++) { // 遍历背包容量
                for (int j = s; j >= 1; j--) { // 遍历背包中物品的个数
                    f[i][j] = max(f[i][j], f[i - v[t]][j - 1] + w[t]); // 更新背包中物品的最大价值
                    if (f[i][j] >= n) // 如果背包中的价值大于等于 n
                        ans = min(ans, i); // 更新 ans 为当前背包容量和之前 ans 中的较小值
                }
            }
        }
        if (ans < 0x3f3f3f3f) // 如果 ans 小于之前初始化的值
            cout << m - ans << endl; // 输出 m 减去 ans 的值
        else
            cout << -1 << endl; // 否则输出 -1
    }
    return 0;
}

 
                
             浙公网安备 33010602011771号
浙公网安备 33010602011771号