0-1背包问题
1.最优子结构性质(用剪贴思想证明)
y1,y2,… yn如果是一个最优解,因若不然,z2,z3,…zn是上述子问题的最优解,从而y1,z2,z3,…zn是0-1背包问题的最优解,这与y1,y2,… yn是最优解矛盾
2.递归关系
m(i,j)=max{m[i+1][j],m(i+1,j-wi)+vi} j>=wi
m[i][j]=m[i+1][j] 0<=j<wi
m[n][j]=vn j>=wn
m[n][j]=0 0<=j<wn
算法代码如下,m[1][c]就是最优解
【回溯得到最优解】:如果m[1][c]=m[2][c],则x1=0,否则x1=1.
当x1=0时,m[2][c]继续构造最优解,当x1=1时,m[2][c-wi]继续构造最优解
#define N 10000 + 1
template<class type>
type knapsack(int n, type c, type v[N], type w[N], type m[N][N], int x[N])
{
int jmax = MY_MIN(w[n] - 1, c);
int i, j;
for (j = 0; j <= jmax; ++j) m[n][j] = 0;
for (j = w[n]; j <= c; ++j) m[n][j] = v[n];
for (i = n - 1; i > 1; --i) {
jmax = MY_MIN(w[i] - 1, c);
for (j = 0; j <= jmax; ++j) m[i][j] = m[i + 1][j];
for (j = w[i]; j <= c; ++j) m[i][j] = MY_MAX(m[i + 1][j], m[i + 1][j - w[i]] + v[i]);
}
m[1][c] = m[2][c];
if (c >= w[1]) m[1][c] = MY_MAX(m[1][c], m[2][c - w[1]] + v[1]);
traceback(n, c, w, v, m, x);
return m[1][c];
}
template<class type>
void traceback(int n, type c, type w[], type v[], type m[N][N], int x[])
{
int i;
for (i = 1; i < n; ++i) {
if (m[i][c] == m[i + 1][c]) x[i] = 0;
else {
x[i] = 1;
c -= w[i];
}
//printf("%d ", x[i]);
}
x[n] = m[n][c] ? 1 : 0;
//printf("%d\n", x[n]);
}
上述算法出了需要n*n的空间外,时间效率需要O(nc),而且要求wi是整数。当背包容量c很大时,需要的时间很多,当c>2^n时,需要O(n2^n)
事实上,计算m(i,j)的递归式在变量j是连续变量,即背包容量为实数时仍成立,可以采用以下方法克服这两个缺点:
n=5, c=10, w={2, 2, 6, 5, 4}, v={6, 3, 5, 4, 6}
由计算m(i,j)的递归式,当i=5时
m(5,j)=6(j>=4)
m(5,j)=0(0<=j<4)
该函数是一个阶梯状函数,所以只需要保存跳跃点。
p[6]={(0, 0)}
q[6] = p[6] + (w5, v5)={(4, 6)}
p[5] = {(0,0), (4,6)}(p[6]并上q[6])
q[5]=p[5]+(w4, v4)={(5,4), (9,10)}
p[4]={(0,0), (4,6), (5,4), (9,10)}但是需要去掉受控跳跃点(5, 4),所以结果为p[4]={(0,0), (4,6), (9,10)}
……
p[1]的最后那个跳跃点(8, 15)就是最优解,改进后的代码如下(通过了SOJ-2111评测):
/******************************************************************************************************
** Copyright (C) 2011.07.01-2013.07.01
** Author: famousDT <13730828587@163.com>
** Edit date: 2011-10-31
******************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>//abs,atof(string to float),atoi,atol,atoll
#include <math.h>//atan,acos,asin,atan2(a,b)(a/b atan),ceil,floor,cos,exp(x)(e^x),fabs,log(for E),log10
#include <vector>
#include <queue>
#include <map>
#include <time.h>
#include <set>
#include <list>
#include <stack>
#include <string>
#include <iostream>
#include <assert.h>
#include <string.h>//memcpy(to,from,count
#include <ctype.h>//character process:isalpha,isdigit,islower,tolower,isblank,iscntrl,isprll
#include <algorithm>
using namespace std;
typedef long long ll;
#define MY_PI acos(-1)
#define MY_MAX(a, b) ((a) > (b) ? (a) : (b))
#define MY_MIN(a, b) ((a) < (b) ? (a) : (b))
#define MY_MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))
#define MY_ABS(a) (((a) >= 0) ? (a) : (-(a)))
#define MY_INT_MAX 0x7fffffff
/*==========================================================*\
| 0-1背包
\*==========================================================*/
#define N 10000 + 1
template<class type>
type knapsack(int n, type c, type v[], type w[], type p[N][2], int x[])
{
int head[N + 2];
head[n + 1] = 0;
p[0][0] = p[0][1] = 0;
int left = 0, right = 0;
int next = 1;
head[n] = 1;
int i, j, k;
for (i = n; i >= 1; --i) {
k = left;
for (j = left; j <= right; ++j) {
if (p[j][0] + w[i]> c) break;
type y = p[j][0] + w[i];
type m = p[j][1] + v[i];
while (k <= right && p[k][0] < y) {
p[next][0] = p[k][0];
p[next++][1] = p[k++][1];
}
if (k <= right && p[k][0] == y) {
if (p[k][1] > m)
m = p[k][1];
++k;
}
if (m > p[next - 1][1]) {
p[next][0] = y;
p[next++][1] = m;
}
while (k <= right && p[k][1] <= p[next - 1][1]) ++k;
}
while (k <= right) {
p[next][0] = p[k][0];
p[next++][1] = p[k++][1];
}
left = right + 1;
right = next - 1;
head[i - 1] = next;
}
traceback(n, w, v, p, head, x);
return p[next - 1][1];
}
template<class type>
void traceback(int n, type w[], type v[], type p[N][2], int *head, int x[])
{
type j = p[head[0] - 1][0];
type m = p[head[0] - 1][1];
int i, k;
for (i = 1; i <= n; ++i) {
x[i] = 0;
for (k = head[i + 1]; k <= head[i] - 1; ++k) {
if (p[k][0] + w[i] == j && p[k][1] + v[i] == m) {
x[i] = 1;
j = p[k][0];
m = p[k][1];
break;
}
}
//printf("%d ", x[i]);
}
//printf("\n");
}
int main()
{
int n, c, v[N], w[N], p[N][2], x[N];
while (scanf("%d%d", &c, &n) == 2) {
int i;
for (i = 1; i <= n; ++i) {
scanf("%d", &w[i]);
}
printf("%d\n", knapsack<int>(n, c, w, w, p, x));
}
return 0;
}