第十周 acm刷题总结,背包

关于背包问题我比较熟悉的目前只是01背包,其他的背包如果能转化,我就都转化成01背包。
01背包刚学的时候最初我是用二维数组记录状态的,后来习惯用一维。
上个例题吧:
Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?

Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
Sample Input

1
5 10
1 2 3 4 5
5 4 3 2 1

Sample Output

14

这是最近做的收集骨头的一个题,是最简单的01背包。代码如下:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

#define ll long long
#define mmax(a, b) a > b ? a : b
#define mmin(a, b) a < b ? a : b

int n, V, v[1004], w[1003];
ll ans[1030];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int t;
	cin >> t;
	while (t--) {
		memset(ans, 0, sizeof(ans));
		cin >> n >> V;
		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 = V; j >= v[i]; j--) {
				ans[j] = mmax(ans[j], ans[j - v[i]] + w[i]);
			}
		}
		cout << ans[V] << endl;
	}
	return 0;
}

再说多重背包吧,多重背包只是比01背包多了一个限制条件,并不是给出几件物品,而是给出几种物品,每种物品都有一定的数量。在这里假设他给的数量很大,那么无疑会大大增加我们代码的时间复杂度,所以有一种优化方法,就像是用二进制能表示处十进制所有数一样,我们用2的乘方数代替他给定的数量,用这些2的乘方数的不同组合,就可以表示每一种取这些物品的可能。关于这种优化,首先我们初始化一个数组备用,之后用它来组合出不同的个数:

int bit[26];
void init() {
        int t = 1;
        for (int i = 0; i < 26; i++) {
        bit[i] = t;
                t *= 2;
        }
}

这样他能组合成的数字已经可以达到非常大了,应该是够用了,如果担心不够用就再多往下存几个,但是如果到了2的30次方,就已经是最大了,31次方就溢出了。
然后他是怎么组合成不同的个数的呢?就是从前面开始取,能取前面的就取前面的,按照1, 2,4, 8, 16, 32…的顺序。比如我现在有20个物品,重量都为2, 价值都为1,我没必要试探20次,用20减1,这是把他分出来形成一个重量位2价值位1的物品,剩19再减2,分出来一个重量位4价值位2的物品,剩17再减去4,分出来一个重量位8价值位4的物品,剩13再减去8,分出来一个重量为16价值为8的物品,剩5想减16减不动了,那剩5就是剩下了一个重量为10价值为5的物品。所以20个相同规格的物品就分成了这样5个不同规格的物品,这5个不同规格的物品可以经过不同的组合组合成每一种对那20个物品的选取情况。这就是2进制优化,大大减少了待考虑的物品的个数。

上个例题:
题目描述

给有一个能承重 V
的背包,和n种物品,每种物品的数量有限多,我们用重量、价值和数量的三元组来表示一个物品,第 i 件物品表示为(Vi,Wi,Si)

,问在背包不超重的情况下,得到物品的最大价值是多少?

54E9C51263E1462585A8F6595841EEC0.jpg
输入

第一行输入两个数V、n

,分别代表背包的最大承重和物品种类数。

接下来 n
行,每行三个数 Vi、Wi、Si,分别代表第 i

种物品的重量、价值和数量。
输出

输出一个整数,代表在背包不超重情况下所装物品的最大价值。
样例输入1

15 4
4 10 5
3 7 4
12 12 2
9 8 7

样例输出1

37

数据规模与约定

时间限制:1 s

内存限制:64 M

60% 的数据保证(Vi≤V≤1000,n≤100,Si≤10,Wi≤1000)​

100% 的数据保证(Vi≤V≤100000,n≤100,Si≤100000,Wi≤1000)

代码如下:

#include <iostream>
#include <iomanip>
using namespace std;

const int M = 200005;
int n, vall, ind, vv[M], ww[M], dp[1000004];
int bit[25];

void init() {
        int t = 1;
        for (int i = 0; i <= 21; i++) {
        bit[i] = t;
                t *= 2;
        }
}

void count(int v, int w, int cnt) {
        int i = 0;
        while (cnt > bit[i]) {
                cnt -= bit[i];
                vv[++ind] = bit[i] * v;
                ww[ind] = bit[i++] * w;
        }
        vv[++ind] = cnt * v;
        ww[ind] = cnt * w;
}

int main() {
        init();
        cin >> vall >> n;
        for (int i = 1; i <= n; i++) {
        int a, b, c;
                cin >> a >> b >> c;
                count(a, b, c);
        }
        for (int i = 1; i <= ind; i++) {
        for (int j = vall; j >= vv[i]; j--) {
                dp[j] = max(dp[j], dp[j - vv[i]] + ww[i]);

                }
        }

        cout << dp[vall] << endl;
        return 0;
}

如果是完全背包,也可以转化成01背包,比如:
例题:
题目描述

有N种物品和一个容量为 V

的背包,每种物品都有无限件可用。

第 i
种物品的体积是Ci,价值是Wi

。求解在不超过背包容量的情况下,能够获得的最大价值。

54E9C51263E1462585A8F6595841EEC0.jpg
输入

第一行为两个整数N、V(1≤N,V≤10000)

,分别代表题目描述中的物品种类数量N和背包容量V。

后跟N行,第 i
行两个整数Ci、Vi

,分别代表每种物品的体积和价值。
输出

输出一个整数,代表可获得的最大价值。
样例输入

5 20
2 3
3 4
10 9
5 2
11 11

样例输出

30

数据规模与约定

时间限制:1s

内存限制:64M

对于100%的数据,1≤N,V≤10000

代码:

#include <iostream>
#include <algorithm>
using namespace std;

int n, vall, v, w, dp[10004];
int bit[26];
void init() {
        int t = 1;
        for (int i = 0; i < 26; i++) {
        bit[i] = t;
                t *= 2;
        }
}
int vv[100004], ww[100005];
int ind = 0;
void count(int v, int w, int cnt) {
        int i = 0;
        while (cnt > bit[i]) {
                cnt -= bit[i];
        vv[++ind] = bit[i] * v;
                ww[ind] = bit[i++] * w;
        }
    vv[++ind] = cnt * v;
        ww[ind] = cnt * w;
}
int main() {
        cin >> n >> vall;
        init();
        for (int i = 1; i <= n; i++) {
                cin >> v >> w;
                count(v, w, vall / v);
        }

        for (int i = 1; i <= ind; i++) {
        for (int j = vall; j >= vv[i]; j--) {
                dp[j] = max(dp[j], dp[j - vv[i]] + ww[i]);
                        //cout << setw(4) << dp[j];
                }
                //cout << endl;
        }
        cout << dp[vall] << endl;
        return 0;
}

如果有特殊的要求,先把特殊条件处理完,再用01背包。比如:
电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
Input
多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。

n=0表示数据结束。

Output
对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。
Sample Input

1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
0

Sample Output

-45
32

这个题最后剩5块的时候再买一个最贵的是最好的了,也就是说把最贵的放到最后买。问题来了,如果留最贵的构不成5快,而之前买了最贵的却能剩5快,会不会更好?不会的, 简单想象一下,如果为了构成5块,我们先买了最贵的,那么剩下5块之后呢?还不是再买一个不那么贵的。无异于留着最贵的最后再买,虽然这个时候剩的钱可能不是恰好五块,但是我最后用最贵的一定可以把以前欠的那些收益拉回来。贪心思想到此为止,剩下的就是背包算法了。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

#define ll long long
#define mmax(a, b) a > b ? a : b
#define mmin(a, b) a < b ? a : b

int n, V, v[1004], w[1003];
ll ans[1003];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	while (cin >> n, n) {
		memset(ans, 0, sizeof(ans));
		for (int i = 1; i <= n; i++) {
			cin >> v[i];
		}
		cin >> V;
		if (V >= 5) {
			nth_element(v + 1, v + n, v + n + 1);
			for (int i = 1; i < n; i++) {
				for (int j = V - 5; j >= v[i]; j--) {
					ans[j] = mmax(ans[j], ans[j - v[i]] + v[i]);
				}
			}
			cout <<V - ans[V - 5] - v[n] << endl;
		}
		else cout << V << endl;
	}
	return 0;
}

再来看一个变式:
The aspiring Roy the Robber has seen a lot of American movies, and knows that the bad guys usually gets caught in the end, often because they become too greedy. He has decided to work in the lucrative business of bank robbery only for a short while, before retiring to a comfortable job at a university.

For a few months now, Roy has been assessing the security of various banks and the amount of cash they hold. He wants to make a calculated risk, and grab as much money as possible.


His mother, Ola, has decided upon a tolerable probability of getting caught. She feels that he is safe enough if the banks he robs together give a probability less than this. 

Input
The first line of input gives T, the number of cases. For each scenario, the first line of input gives a floating point number P, the probability Roy needs to be below, and an integer N, the number of banks he has plans for. Then follow N lines, where line j gives an integer Mj and a floating point number Pj .
Bank j contains Mj millions, and the probability of getting caught from robbing it is Pj .
Output
For each test case, output a line with the maximum number of millions he can expect to get while the probability of getting caught is less than the limit set.

Notes and Constraints
0 < T <= 100
0.0 <= P <= 1.0
0 < N <= 100
0 < Mj <= 100
0.0 <= Pj <= 1.0
A bank goes bankrupt if it is robbed, and you may assume that all probabilities are independent as the police have very low funds.

Sample Input

3
0.04 3
1 0.02
2 0.03
3 0.05
0.06 3
2 0.03
2 0.03
3 0.05
0.10 3
1 0.03
2 0.02
3 0.05

Sample Output

2
4
6

这道题最重要的不是概率的转化,而是理解背包的逆向思维,灵活选取变量作为背包的容量和物品体积及价值。钱是背包容量和物体体积,安全几率是价值,但是求的不是价值,而是利用价值每次取最优的现象,利用单调性求出最多的钱。

#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <cstring>
using namespace std;

#define ll long long
#define mmax(a, b) a > b ? a : b
#define mmin(a, b) a < b ? a : b
#define eps 1e-9

int n, v[105];
double w[103];
double ans[100005];
double W;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin >> t;
	while (t--) {
		cin >> W >> n;	
		W = 1 - W;//概率取对立事件
		ll sum = 0;
		memset(ans, 0, sizeof(ans));
		for (int i = 1; i <= n; i++) {
			cin >> v[i] >> w[i];
			w[i] = 1 - w[i];
			sum += v[i];
		}
		ans[0] = 1;//预处理当不抢钱的时候,百分百安全。
		for (int i = 1; i <= n; i++) {
			for (int j = sum; j >= v[i]; j--) {
				ans[j] = mmax(ans[j], ans[j - v[i]] * w[i]);安全几率是叠加的,这是取对立事件的原因。
			}
		}
		ll aans = 0;
		for (int i = sum; i >= 0; i--) {
			if (ans[i] >= W) { aans = i; break; }从钱多到钱少遍历,一旦安全就跳出,此时的钱就是最多的钱。
		}
		cout << aans << endl;
	}
	return 0;
}
 Recently, iSea went to an ancient country. For such a long time, it was the most wealthy and powerful kingdom in the world. As a result, the people in this country are still very proud even if their nation hasn’t been so wealthy any more.
The merchants were the most typical, each of them only sold exactly one item, the price was Pi, but they would refuse to make a trade with you if your money were less than Qi, and iSea evaluated every item a value Vi.
If he had M units of money, what’s the maximum value iSea could get?

Input
There are several test cases in the input.

Each test case begin with two integers N, M (1 ≤ N ≤ 500, 1 ≤ M ≤ 5000), indicating the items’ number and the initial money.
Then N lines follow, each line contains three numbers Pi, Qi and Vi (1 ≤ Pi ≤ Qi ≤ 100, 1 ≤ Vi ≤ 1000), their meaning is in the description.

The input terminates by end of file marker.

Output
For each test case, output one integer, indicating maximum value iSea could get.

Sample Input

2 10
10 15 10
5 10 5
3 10
5 10 5
3 5 6
2 7 3

Sample Output

5
11

这个不多说了,放物品的先后有原则, 要排序,01背包的话,实际上是从后往前放的,所以排序的时候注意这一点。

#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <cstring>
using namespace std;

#define ll long long
#define mmax(a, b) a > b ? a : b
#define mmin(a, b) a < b ? a : b
#define eps 1e-9

int n, v[105];
double w[103];
double ans[100005];
double W;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin >> t;
	while (t--) {
		cin >> W >> n;	
		W = 1 - W;
		ll sum = 0;
		memset(ans, 0, sizeof(ans));
		for (int i = 1; i <= n; i++) {
			cin >> v[i] >> w[i];
			w[i] = 1 - w[i];
			sum += v[i];
		}
		ans[0] = 1;
		for (int i = 1; i <= n; i++) {
			for (int j = sum; j >= v[i]; j--) {
				ans[j] = mmax(ans[j], ans[j - v[i]] * w[i]);
			}
		}
		ll aans = 0;
		for (int i = sum; i >= 0; i--) {
			if (ans[i] >= W) { aans = i; break; }
		}
		cout << aans << endl;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值