模板

DP问题划分依据:最后一步 划分, 且每个状态的最后一步不同 除了完全背包问题, 其余所有背包问题优化到1维之后都是从大到小循环

for 物品
	for 体积
		for 决策

完全背包:求所有前缀的最大值 多重背包:求滑动窗口的最大值

01 背包

未一维优化

int main()
{
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= m; j++)
            if(v[i] > j) f[i][j] = f[i - 1][j];
            else f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
    cout << f[n][m] << endl; 
}

一维优化

int main()
{
    for(int i = 1; i <= n; i++) 
    {
        for(int j = m; j >= v[i]; j--)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    }
    printf("%d\n",f[m]);
}

完全背包

朴素

int main()
{
    cin >>n ;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    for(int i = 1; i < =n ;i ++)
        for(int j = 0; j <= m; j++)
            for(int k = 0; k * v[i] <= j; k++)
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
    cout << f[n][m] << endl;
}

优化k

int main()
{
    cin >> n;
    for(int i = 1; i <=n ;i++) cin >> v[i] >> w[i];
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= m; j++)
        {
            f[i][j] = f[i - 1][j];
            if(v[i] <= j)
                f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
        }
    cout << f[n][m] << endl;
}

化成一维

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    for(int i = 1; i <=n ;i++)
        for(int j = v[i]; j <= m; j++)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    cout << f[m] << endl;
    return 0;
}

多重背包

数据范围小

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i] >> s[i];
 
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= m; j++)
            for(int k = 0; k <= s[i] && k * v[i] <= j; k++) 
                f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
    cout << f[n][m] << endl;
    return 0;
}

数据范围大

体积和物品数小于2000时

用 2^n(n = 0,1,2,3,…,k)(2^k+1 > s) 分组,为什么说少了很多组却仍能得到正确结果呢? 因为当你枚举到 选 3 个物品时,前面已经枚举过的 选1 和 选2 可以组成 选3, 也就是说 选1 和 选2 的结果跟你 选3 是等价的, 那么我们就可以把 选3 优化掉不去枚举。

怎么跟 01 背包问题扯上关系呢? 你划分之后的物品可以抽象成 “新” 的物品 [打包的物品] (s) (s s) (s s s s ) ( s s s s…) … [对应] 1 2 4 8 … 那么对每个新物品做01背包,便能求出正解。 对于 选3 (s s s),可以由选择过 选1 和 选2 的状态表示,同样 选5 也能由 更小的状态表示出来

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 25000, M = 2010; // N = 1000 * log 2000 (物品种类数 * log 每种物品的个数)
int n,m, cnt; // cnt 将每种物品二进制展开后的数组下标
int v[N],w[N];
int f[M]; // 01背包的一维优化
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        int k = 1;
        while(k  <= c)
        {
            v[++cnt] = a * k;
            w[++cnt] = b * k;
            c -= k;
            k *= 2;
        }
        if(c > 0) // 最后的那个 c
        {
            v[++cnt] = a * c;
            w[++cnt] = b * c;
        }
    }
    int n = cnt;
    for(int i = 1; i <= cnt; i++)
        for(int j = m; j >= v[i]; i--)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    cout << f[m];
    return 0;
}

体积和物品数小于20000时

状态表示依然是前i个物品, 背包体积为j的最大价值。 划分为当前i物品选 0, 1, 2, 3,,, s个

f[i][j] = 
            f[i-1][j],     f[i-1][j-v]+w,   f[i-1][j-2v]+2w,,,f[i-1][j-sv]+sw}
f[i][j-v]= 
            f[i-1][j-v],   f[i-1][j-2v]+w,  f[i-1][j-3v]+2w,,,f[i-1][j-sv]+(s-1)w, f[i-1][j-(s+1)v]+sw}
f[i][j-2v]=
            f[i-1][j-2v],  f[i-1][j-3v]+w,  f[i-1][j-4v]+2w,,,f[i-1][j-sv]+(s-2)w, f[i-1][j-(s+1)v]+(s-1)w, f[i-1][j-(s+2)v]+sw}
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e4 + 10;
int n,m;
int q[N], g[N], f[N];
const int V[] = {1,2,3,4};
const int W[] = {2,4,4,5};
const int S[] = {3,1,3,2};
int main()
{
    //cin >> n >> m;
    for(int i = 0; i < n; i++)
    {
        int v = V[i],w = W[i],s = S[i];
        cin >> v >> w >> s;
        memcpy(g, f, sizeof f);
        for(int r = 0; r < v; r++)
        {
            int hh =0, tt = -1;
            for(int j = r; j <= m; j += v)
            {
                if(hh <= tt && q[hh] < j - s*v) hh++;
                if(hh <= tt) f[j] = max(f[j], g[q[hh]] + (j - q[hh]) / v * w);
                while(hh <= tt && g[q[tt]] - (q[tt] - r)/v*w <= g[j] - (j - r)/v * w)tt--;
                q[++tt] = j;
            }
        }
    }
    cout << f[m] << endl;
    return 0;
}

分组背包

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        cin >> s[i];
        for(int j = 1; j <= s[i]; j++)
        {
            cin >> v[i][j] >> w[i][j];
        }
    }
 
    for(int i = 1; i <= n; i++)
    {
        for(int j = m; j >= 0; j--)
            for(int k = 1; k <= s[i]; k++)
                if(v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
    }
    cout << f[m];
    return 0;
}