背包问题

目录

开端

01背包问题

AcWing 01背包问题

Luogu P2925干草出售

Luogu P1048采药

完全背包问题

AcWing 完全背包问题

Luogu P1853投资的最大效益

多重背包问题

AcWing 多重背包问题 I

AcWing 多重背包问题 II

Luogu P1776宝物筛选

混合背包问题

AcWing 混合背包问题

Luogu P1833樱花

二维费用背包问题

AcWing 二维费用的背包问题 

Luogu P1507NASA的食物计划

分组背包问题

AcWing 分组背包问题

Luogu P1757 通天之分组背包


开端

关于背包问题,嗯一直学不明白,暑假咸的没事又拾起来学了一下,跟着这位大佬整理的思路(背包九讲——全篇详细理解与代码实现-CSDN博客),对背包的思想有了一定清晰的理解,大佬的文章有些长,所以跟着自己的思路再整理一下。

为了方便统一,先定义一下

c[i]:表示代价

w[i]:表示价值

dp[i][j]:表示前i个物品花费代价为j的可以获得的最大代价

p[i]:表示第i种物品最多有p[i]件

01背包问题

定义:

dp[i][j]:表示前i个物品恰放入一个容量为j的背包下可以获得的最大代价

子问题第i1件物品状态:

①不选:dp[i][j]=dp[i-1][j]

②选:dp[i][j]=dp[i][j-c[i]]+w[i]

状态转移方程:

dp[i][j]=max(dp[i-1][j],dp[i][j-c[i]]+w[i])

优化空间复杂度:

O(V*N)

for(int i=1;i<=n;i++)
    for(int j=c[i];j<=V;j--)
        dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);

O(V)

for(int i=1;i<=n;i++)
    for(int j=V;j>=c[i];j++)
        dp[j]=max(dp[j],dp[j-c[i]]+w[i]);

关于顺序和逆序:

逆序表示:dp[j]=max(dp[j],dp[j-c[i]]+w[i])由dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i])转移过来的
顺序表示:dp[j]=max(dp[j],dp[j-c[i]]+w[i])由dp[i][j]=max(dp[i][j],dp[i][j-c[i]]+w[i])转移过来的

初始化问题:

①要求恰好装满:dp[i]=-∞,dp[0]=0;

②只要求价值最大:dp[i]=0;

 AcWing 01背包问题

背包问题_第1张图片

const int N = 1010;
int c[N], w[N], dp[N];
inline void solve()
{
    int N, V;
    cin >> N >> V;
    for (int i = 1; i <= N; i++)
        cin >> c[i] >> w[i];
    for (int i = 1; i <= N; i++)
        for (int j = V; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    cout << dp[V] << endl;
}

Luogu P2925干草出售

const int N = 5e4 + 10;
int w[N], dp[N];
inline void solve()
{
    int C, H;
    cin >> C >> H;
    for (int i = 1; i <= H; i++)
        cin >> w[i];
    for (int i = 1; i <= H; i++)
        for (int j = C; j >= w[i]; j--)
            dp[j] = max(dp[j], dp[j - w[i]] + w[i]);
    cout << dp[C] << endl;
}

Luogu P1048采药

const int N = 1010;
int c[N], w[N], dp[N];
inline void solve()
{
    int T, M;
    cin >> T >> M;
    for (int i = 1; i <= M; i++)
        cin >> c[i] >> w[i];
    for (int i = 1; i <= M; i++)
        for (int j = T; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    cout << dp[T] << endl;
}

完全背包问题

 定义:

dp[i][j]:表示前i种物品恰放入一个容量为j的背包下可以获得的最大代价

子问题第i种物品状态:

①不选该种物品:dp[i][j]=dp[i-1][j];
②选不同件该种物品:选0件、1件、2件……k件:dp[i][j]=dp[i-1][j-c[i]*k]+w[i]*k;

状态转移方程:

dp[i][j]=max(dp[i-1][j-c[i]*k]+w[i]*k)  0<=c[i]*k<=j

优化空间复杂度:

O(N*∑(V/c[i]))

for(int i=1;i<=n;i++)
    for(int j=c[i];j<=V;j++)
        for(int k=0;c[i]*k<=j;k++)
            dp[i][j]=max(dp[i][j],dp[i-1][j-c[i]*k]+w[i]*k);
# 第一个参数,因为k=0时就相当于dp[i-1][j];

O(V*N)转化为01背包问题

for(int i=1;i<=n;i++)
    for(int j=c[i];j<=j;j++)
        dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
//等价于dp[i][j]=max(dp[i-1][j],dp[i][j-c[i]]+w[i]);(不取该物品,取不同件);

关于顺序和逆序:

逆序表示:dp[j]=max(dp[j],dp[j-c[i]]+w[i])由dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i])转移过来的
顺序表示:dp[j]=max(dp[j],dp[j-c[i]]+w[i])由dp[i][j]=max(dp[i-1][j],dp[i][j-c[i]]+w[i])转移过来的

初始化问题:

①要求恰好装满:dp[i]=-∞,dp[0]=0;

②只要求价值最大:dp[i]=0;

AcWing 完全背包问题

背包问题_第2张图片

const int N = 1010;
int c[N], w[N], dp[N];
inline void solve()
{
    int N, V;
    cin >> N >> V;
    for (int i = 1; i <= N; i++)
        cin >> c[i] >> w[i];
    for (int i = 1; i <= N; i++)
        for (int j = c[i]; j <= V; j++)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    cout << dp[V] << endl;
}

Luogu P1853投资的最大效益

const int N = 1e6 + 10;
int c[N], w[N], dp[N];
inline void solve()
{
    int s, n, d;
    cin >> s >> n >> d;
    for (int i = 1; i <= d; i++)
        cin >> c[i] >> w[i];
    while (n--)
    {
        for (int i = 1; i <= d; i++)
            for (int j = c[i]; j <= s; j++)
                dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
        s += dp[s];
    }
    cout << s << endl;
}
int main(

背包问题_第3张图片

这个题目有个小坑

所以要做一下处理:除以1000防止爆空间

const int N = 1e6 + 10;
int c[N], w[N], dp[N];
inline void solve()
{
    int s, n, d;
    cin >> s >> n >> d;
    for (int i = 1; i <= d; i++)
        cin >> c[i] >> w[i];
    while (n--)
    {
        for (int i = 1; i <= d; i++)
            for (int j = c[i] / 1000; j <= s / 1000; j++)
                dp[j] = max(dp[j], dp[j - c[i] / 1000] + w[i]);
        s += dp[s / 1000];
    }
    cout << s << endl;
}

多重背包问题

  定义:

dp[i][j]:表示前i种物品恰放入一个容量为j的背包下可以获得的最大代价

子问题第i种物品状态:

①不选该种物品:dp[i][j]=dp[i-1][j];
②选不同件该种物品:选1件、2件……p[i]件:dp[i][j]=dp[i-1][j-c[i]*k]+w[i]*k;

状态转移方程:

dp[i][j]=max(dp[i-1][j-c[i]*k]+w[i]*k)  0<=k<=p[i]

转化为01背包问题:

方法一:O(V*∑p[i])

for(int i=1;i<=n;i++)
    for(int j=V;j>=c[i];j--)
        for(int k=1;c[i]*k<=j&&k<=p[i];k++)
            dp[j]=max(dp[j],dp[j-c[i]*k]+w[i]*k);
# 第一个参数,因为k=0时就相当于dp[i-1][j];

方法二:二进制优化O(N*log(p)*V)

for (int i = 1; i <= N; i++)
    {
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while (k <= s)  //0……2^k-1部分的系数1,2,4,8……
        {
            cnt++;
            c[cnt] = k * a;
            w[cnt] = k * b;
            s -= k;
            k *= 2;
        }
        if (s > 0)  //2^k……s部分的系数 s-2^k
        {
            cnt++;
            c[cnt] = s * a;
            w[cnt] = s * b;
        }
    }
    N = cnt;  //更新总数量
    for (int i = 1; i <= N; i++)  //01背包问题
        for (int j = V; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
 for (int i = 1; i <= n; i++)
    {
        cin >> c[i] >> w[i] >> p[i];
        int s = min(p[i], W / w[i]);
        for (int k = 1; s > 0; k <<= 1)
        {
            k = min(k, s);
            s -= k;
            for (int j = W; j >= k * w[i]; j--)
            {
                dp[j] = max(dp[j], dp[j - k * w[i]] + k * c[i]);
            }
        }
    }

初始化问题:

①要求恰好装满:dp[i]=-∞,dp[0]=0;

②只要求价值最大:dp[i]=0;

方法一:

AcWing 多重背包问题 I

背包问题_第4张图片

const int N = 110;
int c[N], w[N], p[N], dp[N];
inline void solve()
{
    int N, V;
    cin >> N >> V;
    int cnt = 0;
    for (int i = 1; i <= N; i++)
        cin >> c[i] >> w[i] >> p[i];
    for (int i = 1; i <= N; i++)
        for (int j = V; j >= c[i]; j--)
            for (int k = 1; c[i] * k <= j && k <= p[i]; k++)
                dp[j] = max(dp[j], dp[j - c[i] * k] + w[i] * k);
    cout << dp[V] << endl;
}

方法二:

AcWing 多重背包问题 II

背包问题_第5张图片

const int N = 20010;  //注意初始化,否则会越界
int c[N], w[N], dp[N];
inline void solve()
{
    int N, V;
    cin >> N >> V;
    int cnt = 0;
    for (int i = 1; i <= N; i++)
    {
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while (k <= s)  //0……2^k-1部分的系数1,2,4,8……
        {
            cnt++;
            c[cnt] = k * a;
            w[cnt] = k * b;
            s -= k;
            k *= 2;
        }
        if (s > 0)  //2^k……s部分的系数 s-2^k
        {
            cnt++;
            c[cnt] = s * a;
            w[cnt] = s * b;
        }
    }
    N = cnt;  //更新总数量
    for (int i = 1; i <= N; i++)  //01背包问题
        for (int j = V; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    cout << dp[V] << endl;
}

Luogu P1776宝物筛选

const int N = 1e6 + 10; // 注意初始化,否则会越界
int c[N], w[N], dp[N];
inline void solve()
{
    int n, W;
    cin >> n >> W;
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while (k <= s)
        {
            cnt++;
            w[cnt] = k * a;
            c[cnt] = k * b;
            s -= k;
            k *= 2;
        }
        if (s > 0)
        {
            cnt++;
            w[cnt] = s * a;
            c[cnt] = s * b;
        }
    }
    n = cnt;
    for (int i = 1; i <= n; i++)
        for (int j = W; j >= c[i]; j--)
            dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    cout << dp[W] << endl;
}

简化

const int N = 1e6 + 10; // 注意初始化,否则会越界
int c[N], w[N], p[N], dp[N];
inline void solve()
{
    int n, W;
    cin >> n >> W;
    for (int i = 1; i <= n; i++)
    {
        cin >> c[i] >> w[i] >> p[i];
        int s = min(p[i], W / w[i]);
        for (int k = 1; s > 0; k <<= 1)
        {
            k = min(k, s);
            s -= k;
            for (int j = W; j >= k * w[i]; j--)
            {
                dp[j] = max(dp[j], dp[j - k * w[i]] + k * c[i]);
            }
        }
    }
    cout << dp[W] << endl;
}

混合背包问题

 01背包、完全背包、多重背包的混合状态转移:

for (int i = 1; i <= N; i++)
    {
        cin >> c[i] >> w[i] >> p[i];
        // 01背包
        if (p[i] == -1)
            for (int j = V; j >= c[i]; j--)
                dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
        // 完全背包
        else if (p[i] == 0)
            for (int j = c[i]; j <= V; j++)
                dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
        // 多重背包二进制优化
        else
        {
            int s = min(p[i], V / c[i]);
            for (int k = 1; s > 0; k <<= 1)
            {
                k = max(k, s);
                s -= k;
                for (int j = V; j >= k * c[i]; j--)
                    dp[j] = max(dp[j], dp[j - k * c[i]] + k * w[i]);
            }
        }
    }

AcWing 混合背包问题

背包问题_第6张图片

const int N = 1e6 + 10; // 注意初始化,否则会越界
int c[N], w[N], p[N], dp[N];
inline void solve()
{
    int N, V;
    cin >> N >> V;
    for (int i = 1; i <= N; i++)
    {
        cin >> c[i] >> w[i] >> p[i];
        // 01背包
        if (p[i] == -1)
            for (int j = V; j >= c[i]; j--)
                dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
        // 完全背包
        else if (p[i] == 0)
            for (int j = c[i]; j <= V; j++)
                dp[j] = max(dp[j], dp[j - c[i]] + w[i]);//或将完全背包转化为多重01背包s=V/c[i]
        // 多重背包二进制优化
        else
        {
            int s = min(p[i], V / c[i]);
            for (int k = 1; s > 0; k <<= 1)
            {
                k = min(k, s);
                s -= k;
                for (int j = V; j >= k * c[i]; j--)
                    dp[j] = max(dp[j], dp[j - k * c[i]] + k * w[i]);
            }
        }
    }
    cout << dp[V] << endl;
}

Luogu P1833樱花

const int N = 1e6 + 10; // 注意初始化,否则会越界
int c[N], w[N], p[N], dp[N];
inline void solve()
{
    int m1, m2, s1, s2, N;
    scanf("%d:%d %d:%d %d", &m1, &s1, &m2, &s2, &N);
    int V = m2 * 60 + s2 - m1 * 60 - s1;
    for (int i = 1; i <= N; i++)
    {
        cin >> c[i] >> w[i] >> p[i];
        int s;
        if (p[i] == 0) // 完全转化为多重
            s = V / c[i];
        else
            s = min(p[i], V / c[i]);
        for (int k = 1; s > 0; k <<= 1)
        {
            k = min(k, s);
            s -= k;
            for (int j = V; j >= k * c[i]; j--)
                dp[j] = max(dp[j], dp[j - k * c[i]] + k * w[i]);
        }
    }
    cout << dp[V] << endl;
}

二维费用背包问题

 定义:每件物品需要同时花费两种不同的代价

dp[i][j][k]:表示前i种物品付出两种代价分别最大为j和k时可获得的最大价值

状态转移方程:

dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-c[i]][k-m[i]]+w[i])  

01背包代码(完全背包、多重背包可以类比)

for(int i=1;i<=n;i++)
    for(int j=V;j>=c[i];j--)
        for(int k=M;k>=m[i];k--)
            dp[j][k]=max(dp[j][k],dp[j-c[i]][k-m[i]]+w[i]);

AcWing 二维费用的背包问题 

背包问题_第7张图片

const int N = 1010; // 注意初始化,否则会越界
int c[N], w[N], m[N], dp[N][N];
inline void solve()
{
    int N, V, M;
    cin >> N >> V >> M;

    for (int i = 1; i <= N; i++)
    {
        cin >> c[i] >> m[i] >> w[i];
        for (int j = V; j >= c[i]; j--)
            for (int k = M; k >= m[i]; k--)
                dp[j][k] = max(dp[j][k], dp[j - c[i]][k - m[i]] + w[i]);
    }
    cout << dp[V][M] << endl;
}

Luogu P1507NASA的食物计划

const int N = 1010; // 注意初始化,否则会越界
int c[N], w[N], m[N], dp[N][N];
inline void solve()
{
    int V, M, N;
    cin >> V >> M >> N;

    for (int i = 1; i <= N; i++)
    {
        cin >> c[i] >> m[i] >> w[i];
        for (int j = V; j >= c[i]; j--)
            for (int k = M; k >= m[i]; k--)
                dp[j][k] = max(dp[j][k], dp[j - c[i]][k - m[i]] + w[i]);
    }
    cout << dp[V][M] << endl;
}

分组背包问题

  定义:

dp[k][j]:表示前k组物品花费代价j能取得的最大价值

子问题第k组物品状态:

①不选该组物品:dp[k][j]=dp[k-1][j];
②选该组物品:dp[k][j]=dp[k-1][j-c[i]+w[i]] 物品i属于k组

状态转移方程:

dp[k][j]=max(dp[k-1][j],dp[k-1][j-c[i]]+w[i])  

模板:

 for (int k = 1; k <= N; k++)
    {
        int s;
        cin >> s; // 第k组的物品数量
        for (int i = 1; i <= s; i++)
            cin >> c[i] >> w[i]; // 组中每个物品i的属性
        for (int j = V; j >= 0; j--)
            for (int i = 1; i <= s; i++) // 保证每组物品只能选一个,可以覆盖之前组内物品最优解的来取最大值
                if (j >= c[i])
                    dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    }

AcWing 分组背包问题

背包问题_第8张图片

const int N = 110; // 注意初始化,否则会越界
int c[N], w[N], m[N], dp[N];
inline void solve()
{
    int N, V;
    cin >> N >> V;
    for (int k = 1; k <= N; k++)
    {
        int s;
        cin >> s; // 第k组的物品数量
        for (int i = 1; i <= s; i++)
            cin >> c[i] >> w[i]; // 组中每个物品i的属性
        for (int j = V; j >= 0; j--)
            for (int i = 1; i <= s; i++) // 保证每组物品只能选一个,可以覆盖之前组内物品最优解的来取最大值
                if (j >= c[i])
                    dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
    }
    cout << dp[V] << endl;
}

Luogu P1757 通天之分组背包

const int N = 110;  // 注意初始化,否则会越界
const int M = 1010; // 注意初始化,否则会越界
int c[M], w[M], dp[M];
int g[N][N], b[M]; // g[k][i]表示小组k种第i个物品的编号,b[k]表示小组k的物品+1;
inline void solve()
{
    int N, V;
    cin >> V >> N;
    int t = 0, k = 0;
    for (int i = 1; i <= N; i++)
    {
        cin >> c[i] >> w[i] >> k;
        t = max(t, k);  // 求小组的组数
        b[k]++;         // 小组k的物品+1;
        g[k][b[k]] = i; // 小组k中第b[k]个物品的编号为i;
    }
    for (int k = 1; k <= t; k++)
        for (int j = V; j >= 0; j--)
            for (int i = 1; i <= b[k]; i++)
                if (j >= c[g[k][i]])
                    dp[j] = max(dp[j], dp[j - c[g[k][i]]] + w[g[k][i]]);

    cout << dp[V] << endl;
}

你可能感兴趣的:(洛谷,算法,动态规划,c++)