Algorithm Improvement - Dynamic Programming - Knapsack Problem

When reading this blog, first read this blog about the initialization problem when the volume of the backpack is at most j, exactly j, or at least j, because the most important thing about the dp problem is initialization, determination of the initial state, target state and state transition equation , plus a traversal order and optimization

01 backpack

AcWing 423. Collecting herbs

#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
void solve()
{
    
    
    int T, M;
    std::cin >> T >> M;
    std::vector<int> f(T + 1, 0);
    for (int i = 0; i < M; i ++ )
    {
    
    
        int v, w;
        std::cin >> v >> w; 
        for (int j = T; j >= v; j --)
            f[j] = std::max(f[j], f[j - v] + w);
    }
    std::cout << f[T] << std::endl;
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 1024. Box packing problem

#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
void solve()
{
    
    
    int m, n;
    std::cin >> m >> n;
    std::vector<int> f(m + 1, 0);
    for (int i = 0; i < n; i ++ )
    {
    
    
        int w;
        std::cin >> w;
        for (int j = m; j >= w; j --) 
        {
    
    
            f[j] = std::max(f[j], f[j - w] + w);
        }
    }
    std::cout << m - f[m];
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 1022. Pokemon's Conquest

#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
void solve()
{
    
    
    int n1, m, n2;
    std::cin >> n1 >> m >> n2;
    std::vector<std::vector<int>> f(n1 + 1, std::vector<int>(m + 1, 0));
    for (int i = 0; i < n2; i ++)
    {
    
    
        int  v1, v2;
        std::cin >> v1 >> v2;
        for (int j = n1; j >= v1; j -- )
            for (int k = m - 1; k >= v2; k -- )
            {
    
    
                f[j][k] = std::max(f[j][k], f[j - v1][k - v2] + 1);
            }
    }
    std::cout << f[n1][m - 1] << " " ;
    int k = m - 1;
    while (k > 0 && f[n1][k - 1] == f[n1][m - 1]) k -- ;
    std::cout << m - k << std::endl;
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 278. Number Combinations

#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
void solve()
{
    
    
    int n, m;
    std::cin >> n >> m;
    std::vector<int> f(m + 1, 0);
    f[0] = 1;
    for (int i = 0; i < n; i ++ )
    {
    
    
        int v;
        std::cin >> v;
        for (int j = m; j >= v; j --)
        {
    
    
            f[j] += f[j - v];
        }
    }
    std::cout << f[m];
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 1023. Buy books

#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
void solve()
{
    
    
    int n;
    std::cin >> n;
    std::vector<int> f(n + 1, 0);
    f[0] = 1;
    std::vector<int> v{
    
    10, 20, 50, 100};
    for (int i = 0 ; i < 4; i ++ )
    {
    
    
        for (int j = v[i]; j <= n; j ++ )
        {
    
    
            
            
            f[j] += f[j - v[i]];
        }
    }
    std::cout << f[n];
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 426. Happy Jin Ming

#include <iostream>

const int N = 25 + 1, M = 3 * 1e4 + 10;

int f[M], v[N], w[N];
int m, n;

void solve()
{
    
    
    std::cin >> m >> n;
    for (int i = 0; i < n; i ++ )
    {
    
    
        std::cin >> v[i] >> w[i];
    }
    
    for (int i = 0; i < n; i ++ )
    {
    
    
        for (int j = m; j >= v[i]; j -- )
        {
    
    
            f[j] = std::max(f[j], f[j - v[i]] + v[i] * w[i]);
        }
    }
    
    std::cout << f[m];
    return ;
}

int main()
{
    
    
    solve();
    return 0;
}

full backpack

AcWing 1021. Currency Systems

This question is about counting. The recursion formula is not max or min but +

#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
void solve()
{
    
    
    int n, m;
    std::cin >> n >> m;
    std::vector<long long> f(m + 1, 0), v(n, 0);
    f[0] = 1;
    for (int i = 0; i < n; i ++ ) std::cin >> v[i];
    for (int i = 0; i < n; i ++ )
    {
    
    
        for (int j = v[i]; j <= m; j ++ )
        {
    
    
            f[j] += f[j - v[i]];
        }
    }
    std::cout << f[m];
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 532. Currency System

#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
void solve()
{
    
    
    int n;
    std::cin >> n;
    std::vector<int> a(n, 0);//不该开n + 1的,否则有个0一直在前面
    //int a[25010];
    bool f[25010];
    memset(f, false, sizeof f);
    // std::vector<int> f(n + 1, 0);
    // f.resize(n + 1, false);
    f[0] = 1;
    for (int i = 0; i < n; i ++ )   
    {
    
    
        std::cin >> a[i];
    }
    std::sort(a.begin(), a.end());
    //std::sort(a, a + n);
    int m = a[n - 1];
    int cnt = 0;
    for (int i = 0; i < n; i ++ )
    {
    
    
        if (!f[a[i]]) cnt ++;
        for (int j = a[i]; j <= m; j ++ )
        {
    
    
            f[j] |= f[j - a[i]];
        }
    }
    std::cout << cnt << std::endl;
}
int main()
{
    
    
    int t;
    std::cin >> t;
    while (t -- )
    {
    
    
        solve();
    }
    return 0;
}

Multiple backpacks

AcWing 1019. Celebration Party

This question can also be solved using the monotonic queue optimization method, but it is not necessary. You can solve it by directly traversing the quantity.

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 5 * 1e2 + 10, M = 6 * 1e3 + 10;
int  f[M], s[N], v[N], w[N];
int n, m;

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

Monotone Queue Optimization

AcWing 6. Multiple Knapsack Problem III

Blog 1,
blog 2 , and blog 2 put forward a point of view. Monotonic queue optimization is to split the state. Intuitively, the monotonic queue is a sliding window. Now many topics are divided according to the remainder of the state, and then cooperate with the data structure optimization
Insert image description here

#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>

const int  N = 1e3 + 10, M = 2 * 1e4 + 10;
int f[2][M];
int q[M];
int v[N], w[N], s[N];
void solve()
{
    
    
    int n, m;
    std::cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
    
    
        std::cin >> v[i] >> w[i] >> s[i];
    }
    for (int i = 1; i <= n; i ++ )//要用i-1 & 1 滚动数组因此这里i从1开始
    {
    
    
        for (int j = 0; j < v[i]; j ++ )//j最多取到v-1,这是余数
        {
    
    
            int hh = 0, tt = -1;
            for (int k = j; k <= m; k += v[i])
            {
    
    
                while (hh <= tt && k - s[i] * v[i] > q[hh]) 
                    hh ++;//保证当前的重量k和q[hh]的重量只能差s个物品i。因此我们可以保证队列里面存的重量k最多只能装s个物品i。
                while(hh <= tt && f[(i - 1) & 1][q[tt]] - (q[tt] - j) / v[i] * w[i] <= f[(i - 1) & 1][k] - (k - j) / v[i] * w[i])
                    -- tt;//更新队列,若队列尾部重量的价值小于当前要入队的重量对应的价值,保证队列的头部的价值一定是最大的
                          //这个队列是一个单调递减的单调队列
                
                q[++tt] = k;//保证队列里面至少有一个元素,下面直接使用q[hh]了,也没特判hh <= tt;
                f[i & 1][k] = f[(i - 1) & 1][q[hh]] + (k - q[hh]) / v[i] * w[i];
               
            }
        }
    }
    std::cout << f[n & 1][m];
}
int main()
{
    
    
    solve();
    return 0;
}
#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>

const int  N = 1e3 + 10, M = 2 * 1e4 + 10;
int f[2][M];
int q[M];
int v[N], w[N], s[N];
void solve()
{
    
    
    int n, m;
    std::cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
    
    
        std::cin >> v[i] >> w[i] >> s[i];
    }
    for (int i = 1; i <= n; i ++ )//要用i-1 & 1 滚动数组因此这里i从1开始
    {
    
    
        for (int j = 0; j < v[i]; j ++ )//j最多取到v-1,这是余数
        {
    
    
            int hh = 0, tt = -1;
            for (int k = j; k <= m; k += v[i])
            {
    
    
                while (hh <= tt && k - s[i] * v[i] > q[hh]) 
                    hh ++;//保证当前的重量k和q[hh]的重量只能差s个物品i,同时这也是滑动窗口的精髓,保证一个窗口的大小就是s个物品i
                while(hh <= tt && f[(i - 1) & 1][q[tt]] + (k - q[tt]) / v[i] * w[i] <= f[(i - 1) & 1][k])
                    -- tt;//更新队列,若队列尾部重量的价值小于当前要入队的重量对应的价值,保证队列的头部的价值一定是最大的
                q[++tt] = k;//保证队列里面至少有一个元素,下面直接使用q[hh]了,也没特判hh <= tt;
                f[i & 1][k] = f[(i - 1) & 1][q[hh]] + (k - q[hh]) / v[i] * w[i];
            }
        }
    }
    std::cout << f[n & 1][m];
}
int main()
{
    
    
    solve();
    return 0;
}

The two templates are different in the following places
while(hh <= tt && f[(i - 1) & 1][q[tt]] - (q[tt] - j) / v[i] * w[i] <= f[(i - 1) & 1][k] - (k - j) / v[i] * w[i])
while(hh <= tt && f[(i - 1) & 1][q[tt]] + (k - q[tt]) / v[i] * w[i] <= f[(i - 1) & 1][k])
. It can be found that when updating the tail of the queue, the former judges the value relative to the remainder of j, and
the latter is directly compared with k and q[tt]. It (k - q[tt]) / v[i] * w[i] can be seen from this.
f of both is the maximum value corresponding to directly storing a backpack with weight k.
But in my opinion, the second one is more reasonable

hybrid backpack

AcWing 7. Mixed knapsack problem

The main idea is to find out the quantity of all items and optimize it using binary

#include <iostream>
#include <algorithm>
const int N = 1e3 * 1e2 + 10;//这里N开这么大的原因是,一个物品1000个最多,把它拆成二进制组合,
                             //一个物品就被拆成log(1000)种,一共1e3种物品,就是N的大小就是1e3 * log(1000)+ 10
const int M = 1e3 + 10;
int v[N], w[N], s[N];
int f[M];

int n,m;

void solve()
{
    
    
    std::cin >> n >> m;
    int  cnt = 0;
    //处理io以及预处理一下数据(把01背包、完全背包、多重背包全部都用二进制优化为01背包)
    for (int i = 0; i < n; i ++ )
    {
    
    
        int tv, tw, s;
        std::cin >> tv >> tw >> s;
        if (s == -1) s = 1;
        else if (s == 0) s = m / tv ;//若为完全背包,则在最优情况下,只能取总体积/该物品体积向下取整 
        int k = 1;
        while (k <= s)
        {
    
    
            v[cnt] = k * tv;
            w[cnt] = k * tw;
            s -= k;
            k *= 2;
            cnt ++ ;
        }
        
        if (s > 0)//无法用二进制组成的那部分,可以抽象理解为“余数”
        {
    
    
            v[cnt] = s * tv;
            w[cnt] = s * tw;//这里s在之前不停的减k,就是剩余的余数
            cnt ++;//cnt还要继续++,因为下一个物品还要用呢;
        }
    }
    
    //01背包模版
    for (int i = 0; i < cnt; i ++ )//物品数量是cnt
    {
    
    
        for (int j = m; j >= v[i]; j -- )
        {
    
    
            f[j] = std::max(f[j], f[j - v[i]] + w[i]);
        }
    }
    std::cout << f[m];
}


int main()
{
    
    
    solve();
    return 0;
}

The Knapsack Problem for Two-Dimensional Costs

AcWing 8. The Knapsack Problem for 2D Fees

In fact, it is a derivative of the one-dimensional 01 backpack. From the code, it can be seen that the three states of fi, j, and k can be compressed into two states of jk by me, and the space here can also be optimized. For example, without v[], m[], directly input v of the i-th item, just process it directly when m is

#include <iostream>
#include <algorithm>
const int N = 1e3 + 10, V = 110, M = 110;

int v[N], m[N], w[N], f[V][M];

int n, vmax, mmax;

void solve()
{
    
    
    std::cin >> n >> vmax >> mmax;
    for (int i = 0; i < n; i ++ )
    {
    
    
        std::cin >> v[i] >> m[i] >> w[i];
    }
    for (int i = 0; i < n; i ++ )
    {
    
    
        for (int j = vmax; j >= v[i]; j -- )
        {
    
    
            for (int k = mmax; k >= m[i]; k --)
            {
    
    
                f[j][k] = std::max(f[j][k], f[j - v[i]][k - m[i]] + w[i]);
            }
        }
    }
    std::cout << f[vmax][mmax];
}
int main()
{
    
    
    solve();    
    return 0;
}

AcWing 1020. Diver

This question is a template for two-dimensional costs + no less than.
I haven't done it exactly to this problem, but here is a template to remember no less than. The definition of the target state and initial state of dynamic programming
in this blog is something I haven't learned before. This knowledge point can help us understand the dynamics What should be output as a result of planning, and the most important thing is some details during initialization.

Below is the comment section, which I think is very well said.
Insert image description here

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

const int N = 1e3 + 10, V1 = 21 + 1, V2 = 79 + 1;//记得至少加一,因为遍历的时候遍历到了v1max,v2max
int v1[N], v2[N], w[N], f[V1][V2];

int v1min, v2min;
int n;
void solve()
{
    
    
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    std::cin >> v1min >> v2min;
    std::cin >> n;
    for (int i = 0; i < n; i ++ )
    {
    
    
        std::cin >> v1[i] >> v2[i] >> w[i];
    }
    
    for (int i = 0; i < n; i ++ )
    {
    
    
        for (int j = v1min; j >= 0; j -- )//这里不是v1[i],下面的状态转移方程也不是简单的max
        {
    
    
            for (int k = v2min; k >= 0; k --)
            {
    
    
                f[j][k] = std::min(f[j][k], f[std::max(j - v1[i], 0)][std::max(k - v2[i], 0)] + w[i]);
                //f[j][k] = std::min(f[j][k], f[j - v1[i]][k - v2[i]] + w[i]); 之前是这么写的,但是wa了
                //这题的初始状态是f0,j,k(j,k <= 0)不是f0,0,0,要把所有<0的状态转移到0上,因此要取max
            }
        }
    }
    std::cout << f[v1min][v2min];
}

int main()
{
    
    
    solve();
    return 0;
}

group backpack

AcWing 1013. Machine Allocation + Looking for specific solutions

#include <iostream>

using namespace std;

const int N = 11, M = 16;

int n, m;
int w[N][M];
int f[N][M];//分组dp,一开始想的是f[M]01背包那样,后来发现如果遍历到一个公司的时候,这样可能会选择同一行的多列,实际上题意是一组选一个,实际上就是一个分组背包
int path[N], cnt = 1;//这里path不用记录公司的编号,遍历的时候直接顺序遍历就行了,因为有的公司没选就是0么

void dfs(int i, int j)
{
    
    
    if (!i) return;
    //寻找当前状态f[i][j]是从上述哪一个f[i-1][k]状态转移过来的,
    for (int a = 0; a <= j; ++ a)
    {
    
    
        if (f[i - 1][j - a] + w[i][a] == f[i][j])
        {
    
    
            path[cnt ++ ] = a;
            dfs(i - 1, j - a);
            return;
        }
    }
}
int main()
{
    
    
    //input
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            cin >> w[i][j];

    //dp
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            for (int k = 0; k <= j; ++ k)
                f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
    cout << f[n][m] << endl;

    //find path
    dfs(n, m);
    for (int i = cnt - 1, id = 1; i >= 1; -- i, ++ id)//这里必须倒序输出,因为path里面记录的是逆序的,我们输出的时候要从第一个公司开始输出
        cout << id << " " << path[i] << endl;
    return 0;
}

AcWing 487. Jin Ming’s Budget Plan


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

using namespace std;
const int M = 32010, N = 61;

typedef pair<int, int> PII;

#define v first
#define w second

PII master[N];
int f[M];
std::vector<PII> servent[N];
int n, m;


void solve()
{
    
    
    std::cin >> m >> n;//好恶心的题目,先输入m在输入n
    for (int i = 1; i <= n; ++ i)
    {
    
    
        int v, p, q;
        std::cin >> v >> p >> q;
        p *= v;//直接存的是w其实
        if (!q) master[i] = {
    
    v, p};//这里{v, p}其实是一个结构体,可以直接master[i].first这样用或者.v,因为我们之前定义了v为first
        else servent[q].push_back({
    
    v, p});
    }
    
    for (int i = 1; i <= n; ++ i)
    {
    
    
        for (int j = m; j >= 0; -- j)
        {
    
    
            //二进制枚举
            for (int u = 0; u < 1 << servent[i].size(); ++ u)//二进制列出一个组合的数量,如果i有k个附件,那么i组就有2^k个组合,可以看y总的题解
            {
    
    
                int v = master[i].v, w =master[i].w;//先把主件选上
                for (int k = 0; k < servent[i].size(); ++ k)//枚举第i组的每种组合,并计算v和w
                {
    
    
                    if (u >> k & 1)
                    {
    
    
                        v += servent[i][k].v;
                        w += servent[i][k].w;
                    }
                }
                if (j >= v)
                f[j] = std::max(f[j], f[j - v] + w);
            }
        }
    }
    std::cout << f[m];
    return;
}
int main()
{
    
    
    solve();
    return 0;
}

Tree dp + backpack

AcWing 10. Backpack problem with dependencies

The status definitions of the two methods are the same. I personally think the second method is more reasonable regarding the specific DP process. It does not have as many patches as the first method. It's easier for me to understand tree dp.
But method 1’s blog about state dependence combined with tree-shaped dp compared our previous linear dependence is well written, which helped me understand the state definition of f[u][j].
Method 1: Refer to the pencil boss’s blog

#include <iostream>
#include <cstring>

const int N = 110, V = 110;

int f[N][V];//f[u][j]的定义是节点u给子树分配j体积的时候可以获得的最大价值
int h[N], e[N], ne[N], idx;
int v[N], w[N];
int n, m;
int root;

void add(int a, int b)
{
    
    
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u)
{
    
    
    for (int i = h[u]; ~i; i = ne[i])
    {
    
    
        int son = e[i];
        dfs(son);
        for (int j = m - v[u]; j >= 0 ; -- j)//分给子树son的体积,j从m - v[u]开始是预留一个v[u]给选择u节点
        {
    
    
            for (int k = 0; k <= j; ++ k)//遍历子树son的体积
            {
    
    
                f[u][j] = std::max(f[u][j], f[u][j - k] + f[son][k]);
            }
        }
        
    }
    //初始化
    for (int j = m; j >= v[u]; -- j) f[u][j] = f[u][j - v[u]] + w[u];//最后选上u,之前已经预留体积了
    for (int j = 0; j < v[u]; ++ j) f[u][j] = 0; //初始化,如果体积不够选择u,那么f[u][j]=0,这也符合我们对f[u][j]这个状态的定义
    return ;
}
void solve()
{
    
    
    std::cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ )
    {
    
    
        int p;
        //体积,价值,依赖物品编号
        std::cin >> v[i] >> w[i] >> p;
        if (p == -1) root = i;
        else add(p, i);
    }
    
    dfs(root);
    std::cout << f[root][m];// 目标状态是f[root][m],不是f[n][m]
    return;
}

int main()
{
    
    
    solve();
    return 0;
}

Method 2: Refer to the blogs of unknown bloggers

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

const int N = 110, M = 110;

std::vector<int> g[N];
int f[N][M];
int v[N], w[N];
int n, m;
int root, p;

void dfs(int u)
{
    
    
    //状态初始化,先选择当前的节点u
    for (int j = v[u]; j <= m; ++ j) f[u][j] = w[u];
    
    for (int i = 0; i < g[u].size(); ++ i)
    {
    
    
        int son = g[u][i];
        dfs(son);
        //当前树可用体积为m,但是我们只需要更新的状态是f[u][m ~v[u]],因为j<v[u]的时候就是0,不需要更新,初始化默认就是0,这里算一个剪枝优化
        for (int j = m; j >= v[u]; -- j)//确定子树最大可用体积
        {
    
    
            for (int k = 0; k <= j - v[u]; ++ k)//从小到大遍历子树可以用的体积
            {
    
    
                //k实际意义就是子树可用体积,因为我们定义j的时候是从m开始,不是m-v[u],因此我们这里最多遍历到j-v[u]
                f[u][j] = std::max(f[u][j], f[u][j - k] + f[son][k]);
            }
        }
    }
}

void solve()
{
    
    
    std::cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
    {
    
    
        //体积,价值,依赖的编号
        std::cin >> v[i] >> w[i] >> p;
        if (p == -1) root = i;
        else g[p].push_back(i);
    }
    
    dfs(root);
    std::cout << f[root][m];
}

int main()
{
    
    
    solve();
    return 0;
}

Maximum value + Weight does not exceed backpack capacity + Find the number of solutions

Usually when we write 01 backpack, we only have one constraint – the number of options that “the weight does not exceed the capacity of the backpack”. Here we also need to ensure the maximum value.
Code that I usually write:

#include <iostream>

using namespace std;

const int N = 110;

int n, m;
int f[N];

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

    cout << f[m] << endl;

    return 0;
}

Of course, there is also a complete knapsack + the volume is exactly m + the number of solutions, all in this blog . This blog also points out the research on the initialization problem that the volume is at most j, exactly j, or at least j.

At the same time, regarding the initialization of exactly and no more than (not greater than), Pencil said this, which is also the same as what the blog above said.
Insert image description here

This question cannot directly output g[m], because the question is about the number of solutions whose weight does not exceed m to reach the maximum value, not exactly. There is a possibility that the weight has reached the maximum value before m is used. These solutions Even if we count, we must collect them one by one

#include <iostream>

const int N = 1e3 + 10, M = 1e3 + 10, mod = 1e9 + 7;

int g[M], f[M];

int n, m;

void solve()
{
    
    
    std::cin >> n >> m;
    
    int v, w;
    
    f[0] = 0;
    g[0] = 1;
    
    for (int i = 1; i <= n; ++ i)
    {
    
    
        std::cin >> v >> w;
        for (int j = m; j >= v; -- j)
        {
    
    
            int c = 0;
            //因为还需要f[j],和f[j - v] + w,这两个状态用来状态转移,因此不能直接取max更新f[j]
            //c同理也是,我们还需要g[j]这个状态用来状态转移,因此不能直接更新g[j];
            int temp =  std::max(f[j], f[j - v] + w);
            if (temp == f[j]) c += g[j] % mod;//这里的g[j]其实是g[i - 1][j]
            if (temp == f[j - v] + w) c += g[j - v] % mod;//这里的g[j - v]其实是g[i - 1][j - v],这就是倒序遍历j的好处
            f[j] = temp;
            g[j] = c;
        }
    }
    
    //因为题目要求的是重量不超过m的方案数,因此我们需要遍历重量从0~m所有的g,可能重量没到m就已经达到最大价值了
    int res = 0;
    for (int j = 0; j <= m; ++ j)
    {
    
    
        if (f[j] == f[m]) res = (res + g[j]) % mod;
    }
    std::cout << res;
    return ;
}

int main()
{
    
    
    solve();
    return 0;
}

01 Backpack + Dictionary order minimum + Looking for specific solutions

AcWing 12. Find a specific solution to the knapsack problem

This question seeks the specific solution with the smallest lexicographical order. We can look at it in combination with the question of machine allocation. That question uses recursion to find the specific solution. This question is an iterative method. At the same time, because the smallest lexicographical order is required, the order of dp is The reverse order also leads to a difference in the definition of the state. f[i][j] represents the value when the i-th item to the n-th item can be selected + the weight is j, instead of selecting the first i item.
Pencil Master’s Blog

#include <iostream>
#include <algorithm>
const int N = 1e3 + 10, M = 1e3 + 10;
int f[N][M];//f[i][j]表示从第i到第n个物品,而不是后i个物品,这点铅笔大佬博客中写错了
//虽然这里是01背包,但是,题目要求求出具体的方案,
//因此我们不能边dp边求出具体方案(无法将i,j压缩到一层状态去表示),我们需要求出具体方案后去倒推具体方案
//倒推的时候我们需要依赖i这一层状态,只靠j这一层状态是不行的
int v[N], w[N];
int n, m;
int path[N], cnt;

void solve()
{
    
    
    std::cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
    {
    
    
        std::cin >> v[i] >> w[i];
    }
    
    //这里题目要求输出字典序最小的方案,即选或不选都行的时候我们手动选择
    //那么不如直接倒序dp(这样我们找字典序最小的方案时直接正序遍历即可),
    //dp[i + 1][j]表示不选第i个物品,dp[i + 1][j - v[i]] + w[i]表示选择第i个物品
    
    //如果不是要求我们输出字典序最小的方案,我们当然可以正序dp,然后再正序倒叙遍历寻找转移路径都行,只要找到一组解就行
    for (int i = n; i >= 1; -- i)
    {
    
    
        for (int j = 0; j <= m; ++ j)
        {
    
    
            f[i][j] = f[i + 1][j];//初始化f[i][j]先
            if (j >= v[i]) f[i][j] = std::max(f[i][j], f[i + 1][j - v[i]] + w[i]);
            
            
            /*下面这种写法会WA,
            我现在知道原因了,如果不满足j>=v[i]的时候,f[i][j]会没有赋值,所以wa了
            
            if (j >= v[i]) f[i][j] = std::max(f[i + 1][j], f[i + 1][j - v[i]] + w[i]);
            */
        }
    }
    
    //迭代法进行路径追踪
    for (int i = 1, j = m; i <= n; ++ i)
    {
    
    
        if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i]) //选或不选第i个物品都行,为了字典序最小,我们手动选择
        {
    
    
            path[cnt ++ ] = i;
            j -= v[i];
        }
    }
    
    for (int i = 0; i < cnt; ++ i)
    {
    
    
        std::cout << path[i] << " ";
    }
}
int main()
{
    
    
    solve();
    return 0;
}

01 backpack plus + greedy (neighbor exchange)

Neighbor exchange is a very classic greedy method. For details, you can read the introduction in this blog and you will recall it quickly.

#include <iostream>
#include <algorithm>
#include <cstring>
const int N = 110, M = 1e4 + 10;//最多花费多少时间
int f[M];

struct stone {
    
    
    int s, e, l;
    bool operator < (const stone &t) const{
    
    
        return s * t.l < t.s * l;
    }
}a[N];

int n, m;
int casecnt;

void solve()
{
    
    
    ++ casecnt;
    //多组测试数据需要初始化
    m = 0;
    memset(f, -0x3f, sizeof f);
    f[0] = 0;//这里的dp数组初始化是“恰好”的初始化
    
    std::cin >> n;//这里m要自己算
    for (int i = 1; i <= n; ++ i)
    {
    
    
        std::cin >> a[i].s >> a[i].e >> a[i].l;
        m += a[i].s;
    }
    
    std::sort(a + 1, a + 1 + n);//贪心排个序
    
    for (int i = 1; i <= n; ++ i)
    {
    
    
        for (int j = m; j >= a[i].s; -- j)
        {
    
    
            //注意这个dp式子中是std::max(0, a[i].e - (j - a[i].s) * a[i].l)
            //      一开始写成了std::max(0, a[i].e - j * a[i].l)
            //题目说的是只要开始吃就可以获得所有能量,因此要减去吃第i块石头的时间
            f[j] = std::max(f[j], f[j - a[i].s] + std::max(0, a[i].e - (j - a[i].s) * a[i].l));
        }
    }
    
    int res = 0;
    
    for (int j = 0; j <= m; ++ j)
    {
    
    
        res = std::max(res, f[j]);
    }
    std::cout << "Case #" << casecnt << ": " << res << std::endl;
}

int main()
{
    
    
    int T;
    std::cin >> T;
    while (T -- )
    {
    
    
        solve();
    }
    return 0;
}

Guess you like

Origin blog.csdn.net/chirou_/article/details/131504623