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 <[email protected]> ** 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; }