背包九讲 c++实现完整代码

原文链接:

http://love-oriented.com/pack/pack2alpha1.pdf

1. 01背包

1)题目:

有n件物品和一个容量为v的背包。
第i件物品的费用是c[i],价值是w[i]。
求解将哪些物品装入背包可使价值总和最大

2)输入:

测试用例数 
物品数 背包大小 
n个物品的ci和wi

3)代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxV 1000

int main(void) {
	int times, n, v, ci, wi;
	int f[maxV];
	freopen("1.txt", "r", stdin);
	cin >> times;
	while (times--) {
		memset(f, 0, sizeof(f));
		cin >> n >> v;
		for (int i = 0; i < n; i++) {
			cin >> ci >> wi;
			for (int j = v; j >= 0; j--) {
				if (j >= ci)
					f[j] = max(f[j - ci] + wi, f[j]);
			}
		}
		for (int i = 0; i <= v; i++) cout << f[i] << " ";
		cout << endl;
		cout << f[v] << endl;
	}
}

4)测试文件:

2
4 10
2 4
3 5
4 6
5 10

5 20
3 2
7 3
10 5
15 6
16 10


2. 完全背包

1)题目:

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

2)输入:

测试用例数 
物品数 背包容量
n个物品的ci和vi

3)代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
#define maxV 1000

int main(void) {
	int cases, n, v, ci, wi;
	int f[maxV];
	freopen("2.txt", "r", stdin);
	cin >> cases;
	while (cases--) {
		memset(f, 0, sizeof(f));
		cin >> n >> v;
		for (int i = 0; i < n; i++) {
			cin >> ci >> wi;
			for (int j = 0; j <= v; j++) {
				if (j >= ci)
					f[j] = max(f[j], f[j - ci] + wi);
			}
		}
		cout << f[v] << endl;
	}
}

4)测试文件:

2
4 10
2 4
3 5
4 6
5 10

5 20
3 2
7 3
10 5
15 6
16 10


3. 多重背包

1)题目:

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

2)输入:

测试用例数 
物品数 背包容量
n个物品的ni ci wi

3)代码:

#include <iostream>
#include <algorithm>
using namespace std;
#define maxV 1000

int f[maxV], v;

void ZeroOnePack(int ci, int wi) {
	for (int j = v; j >= 0; j--) 
		if (j >= ci)
			f[j] = max(f[j], f[j - ci] + wi);
}

void CompletePack(int ci, int wi) {
	for (int j = 0; j <= v; j++) 
		if (j >= ci)
			f[j] = max(f[j], f[j - ci] + wi);
}

void MultiplePack(int ni, int ci, int wi) {
	if (ni * ci >= v) {
		CompletePack(ci, wi);
		return;
	}
	int k = 1, amount = ni;
	while (k < ni) {
		ZeroOnePack(ci * k, wi * k);
		amount -= k;
		k *= 2;
	}
	ZeroOnePack(ci * amount, wi * amount);
}

int main(void) {
	int cases, n, ni, ci, wi;
	freopen("3.txt", "r", stdin);
	cin >> cases;
	while (cases--) {
		memset(f, 0, sizeof(f));
		cin >> n >> v;
		for (int i = 0; i < n; i++) {
			cin >> ni >> ci >> wi;
			MultiplePack(ni, ci, wi);
		}
		//for (int i = 0; i <= v; i++) cout << f[i] << " "; cout << endl;
		cout << f[v] << endl;
	}
}

4)测试文件:

2 
3 10
1 1 10
2 2 4
3 3 11

4 20
5 2 5
2 3 6
3 4 8
1 6 19


4. 混合三种背包

1)问题:

有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。
应该怎么求解呢?

2)输入:

测试用例数
物品数 背包容量
第i种物品的ni(无限次的标为-1) ci wi

3)代码:

#include <iostream>
#include <cstring>
using namespace std;
#define maxV 1000

int f[maxV], v;

void ZeroOnePack(int ci, int wi) {
	for (int j = v; j >= 0; j--) 
		if (j >= ci)
			f[j] = max(f[j], f[j - ci] + wi);
}

void CompletePack(int ci, int wi) {
	for (int j = 0; j <= v; j++) 
		if (j >= ci)
			f[j] = max(f[j], f[j - ci] + wi);
}

void MultiplePack(int ni, int ci, int wi) {
	if (ni * ci >= v) {
		CompletePack(ci, wi);
		return;
	}
	int k = 1, amount = ni;
	while (k < ni) {
		ZeroOnePack(ci * k, wi * k);
		amount -= k;
		k *= 2;
	}
	ZeroOnePack(ci * amount, wi * amount);
}

int main(void) {
	int cases, n, ni, ci, wi;
	freopen("4.txt", "r", stdin);
	cin >> cases;
	while (cases--) {
		memset(f, 0, sizeof(f));
		cin >> n >> v;
		for (int i = 0; i < n; i++) {
			cin >> ni >> ci >> wi;
			if (ni == 1) ZeroOnePack(ci, wi);
			else if (ni == -1) CompletePack(ci, wi);
			else MultiplePack(ni, ci, wi);
		}
		for (int i = 0; i <= v; i++) cout << f[i] << " "; cout << endl;
		cout << f[v] << endl;
	}
}

4)测试文件:

2
3 10
-1 1 2
2 2 5
1 3 7

4 20
3 2 5
4 4 6
-1 6 8
1 7 10


5. 二维费用的背包

1)问题:

对于每件物品,具有两种不同的费用;
选择这件物品必须同时付出这两种代价;
对于每种代价都有一个可付出的最大值(背包容量)。
问怎样选择物品可以得到最大的价值。
第i件物品所需的两种代价分别为a[i]和b[i]。
两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w[i]。

2)输入:

测试用例数
物品数 第一个背包容量v 第二个背包容量u
第i个物品的ai bi wi

3)代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
#define maxV 1000
#define maxU 1000

int main(void) {
	int cases, n, v, u, ai, bi, wi;
	int f[maxV][maxU];
	freopen("5.txt", "r", stdin);
	cin >> cases;
	while (cases--) {
		memset(f, 0, sizeof(f));
		cin >> n >> v >> u;
		for (int i = 0; i < n; i++) {
			cin >> ai >> bi >> wi;
			for (int j = v; j >= 0; j--) {
				for (int k = u; k >= 0; k--) {
					if (ai <= j && bi <= k)
						f[j][k] = max(f[j][k], f[j - ai][k - bi] + wi);
				}
			}
		}
		cout << f[v][u] << endl;
	}
}

4)测试文件:

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

4 5 6
1 1 3
1 2 4
2 1 4
3 4 7



6. 分组背包

1)问题:

有n件物品和一个容量为v的背包。
第i件物品的费用是c[i],价值是w[i]。
这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。
求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

2)输入:

测试用例数 
物品数 背包容量 组数g(组号范围:0 ~ g-1)
第i件物品的ci wi gi(所属组号)

3)代码:

#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>

using namespace std;
#define maxV 1000
#define maxG 100
#define maxN 100

int main(void) {
	int cases, n, v, g, gi;
	int f[maxV];
	int ci[maxN], wi[maxN];
	freopen("6.txt", "r", stdin);
	cin >> cases;
	while (cases--) {
		memset(f, 0, sizeof(f));
		cin >> n >> v >> g;
		vector<vector<int> > gMap(g);
		for (int i = 0; i < n; i++) {
			cin >> ci[i] >> wi[i] >> gi;
			gMap[gi].push_back(i);
		}

		for (int i = 0; i < g; i++) {
			for (int j = v; j >= 0; j--) {
				for (int k = 0; k < gMap[i].size(); k++) {
					if (j >= ci[gMap[i][k]])
						f[j] = max(f[j], f[j - ci[gMap[i][k]]] + wi[gMap[i][k]]);
				}
			}
		}

		cout << f[v] << endl;
	}
}

4)测试文件:

2
4 10 2
3 5 0
4 6 0
2 7 1
1 6 1

5 10 3
1 3 0
2 4 0
3 5 1
4 7 1
5 8 2


7. 有依赖的背包

1)题目:

这种背包问题的物品间存在某种“依赖”的关系。
也就是说,i依赖于j,表示若选物品i,则必须选物品j。
为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;
另外,没有某件物品同时依赖多件物品。

2)输入:

测试用例数 
物品数 背包大小
第i个物品的ci wi di(依赖物品的编号,-1为不依赖其他物品)

3)代码:

#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>

using namespace std;
#define maxV 1000
#define maxG 100

int main(void) {
	int cases, n, v, ci[maxV], wi[maxV], di, f[maxV];
	freopen("7.txt", "r", stdin);
	cin >> cases;
	while (cases--) {
		memset(f, 0, sizeof(f));
		cin >> n >> v;
		
		// group[i]空表示i号物品有依赖,因此存放到其他组里
		// 只有一个元素表示i号物品既无依赖也不被依赖
		// 有多个元素表示i号物品被依赖,这里自己的编号i也被存放进group[i]
		vector<vector<int> > groups(n); 

		// 读入数据并储存起来
		for (int i = 0; i < n; i++) {
			cin >> ci[i] >> wi[i] >> di;
			if (di == -1) groups[i].push_back(i);
			else groups[di].push_back(i);
		}

		// 对每个有多个元素的组进行01背包
		for (int i = 0; i < n; i++) {
			if (groups[i].size() > 1) {
				vector<int> giOri; //group[i]的拷贝,排除i本身
				int newV = v - ci[i];

				// 复制group[i]中的元素,排除i
				for (int j = 0; j < groups[i].size(); j++) {
					if (groups[i][j] != i)
						giOri.push_back(groups[i][j]);
				}

				// 把等效物品组存入group[i]中
				groups[i].clear();
				groups[i].resize(newV + 1, 0);
				for (int j = 0; j < giOri.size(); j++) {
					for (int k = newV; k >= 0; k--) {
						if (ci[giOri[j]] <= k) {
							groups[i][k] = max(groups[i][k], groups[i][k - ci[giOri[j]]] + wi[giOri[j]]);
						}
					}
				}
			}
		}

		// 进行分组背包
		for (int i = 0; i < n; i++) {
			if (groups[i].size() == 0) continue;
			else if (groups[i].size() == 1) { // i物品无依赖且不被依赖
				for (int j = v; j >=0; j--) {
					if (j >= ci[i])
						f[j] = max(f[j], f[j - ci[i]] + wi[i]);
				}
			} else { // i物品被依赖, i组第k个物品的cost为k + ci[i], weight为group[i][k] + wi[i]
				for (int j = v; j >= 0; j--) { 
					for (int k = 0; k < groups[i].size(); k++) {
						if (j >= k + ci[i])
							f[j] = max(f[j], f[j - k - ci[i]] + groups[i][k] + wi[i]);
					}
				}
			}
		}
		
		cout << f[v] << endl;
	}
}

4)测试文件:

2
3 10
3 5 -1
4 6 0
7 10 0

4 15
4 1 -1
6 8 0
7 10 0
10 4 -1


8. 泛化物品

1)题目:

在背包容量为v的背包问题中,泛化物品是一个定义域为0..v中的整数的函数h,当分配给它的费用为v时,能得到的价值就是h(v)。
为了应用泛化物品思想,这里假设题目为:存在依赖关系树(简单起见,只有一个根节点),即有依赖的物品也可以被依赖,可结合原文7.3节来理解此句。

2)输入:

测试用例数
物品数 背包容量 根节点编号
第i个物品的ci wi di(依赖物品的编号,-1为不依赖其他物品)

3)代码:

#include <iostream>
#include <algorithm>
using namespace std;
#define maxN 100
#define maxV 1000

int n, v;
int cnt = 0;
int head[maxN];
int wi[maxN], ci[maxN];
int f[maxN][maxV];

struct Edge {
	int v, next;
} e[maxN - 1];

void addEdge(int u, int v) {
	e[cnt].v = v;
	e[cnt].next = head[u];
	head[u] = cnt++;
}

void treeDP(int u) {
	for (int i = ci[u]; i <= v; i++) {
		f[u][i] = wi[u];
	}
	for (int i = head[u]; i != -1; i = e[i].next) {
		int curr = e[i].v;
		treeDP(curr);
		for (int j = v; j >= 0; j--) {
			for (int k = j - ci[u]; k >= 0; k--) {
				f[u][j] = max(f[u][j], f[u][j - k] + f[curr][k]);
			}
		}
	}
}

int main(void) {
	int cases, root;
	freopen("8.txt", "r", stdin);
	cin >> cases;
	while (cases--) {
		cnt = 0;
		memset(head, -1, sizeof(head));
		memset(f, 0, sizeof(f));
		cin >> n >> v >> root;
		for (int i = 0; i < n; i++) {
			int di;
			cin >> ci[i] >> wi[i] >> di;
			addEdge(di, i);
		}
		treeDP(root);
		cout << f[root][v] << endl;
	}
}

4)测试文件:

2
5 5 0
1 2 -1
2 7 0
2 5 0
2 6 2
1 4 2

6 5 0
1 2 -1
1 3 0
2 3 0
2 4 0
2 7 2
3 10 2


9. 背包问题的变化

1)题目:

输出01背包的具体方案

2)输入:

同01背包

3)代码:

#include <iostream>
#include <algorithm>
using namespace std;
#define maxV 1000
#define maxN 100

int main(void) {
	int cases, n, v, ci[maxN], wi;
	int f[maxV];
	bool g[maxN][maxV]; //g[i][v]=0 表示没放i时的f(i, v)较大,
						//g[i][v]=1 表示放进i时的f(i, v)较大
	freopen("9.txt", "r", stdin);
	cin >> cases;
	while (cases--) {
		memset(f, 0, sizeof(f));
		memset(g, 0, sizeof(g));
		cin >> n >> v;
		for (int i = 0; i < n; i++) {
			cin >> ci[i] >> wi;
			for (int j = v; j >= 0; j--) {
				if (j >= ci[i]) {
					if (f[j - ci[i]] + wi > f[j]) {
						f[j] = f[j - ci[i]] + wi;
						g[i][j] = 1;
					}
				}
			}
		}

		int i = n - 1, j = v;
		while (i >= 0) {
			if (g[i][j] == 1) {
				cout << "选了" << i << endl;
				j -= ci[i];
			} else {
				cout << "没选" << i << endl;
			}
			i--;
		}
		cout << endl;
	}
}

4)测试文件:

2
4 10
2 4
3 5
4 6
5 10

5 20
3 2
7 3
10 5
15 6
16 10




你可能感兴趣的:(动态规划,背包问题,背包九讲)