数字三角形,最长上升子序列,背包模型 AcWing算法提高课 (详解)

目录

数字三角形模型(只能向右和向下或向左和向上)

AcWing 1015. 摘花生

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 110;

int f[N][N];
int g[N][N];

int main()
{
    int _; cin >> _;
    
    while (_ -- )
    {
        int r, c;
        cin >> r >> c;
        for (int i = 1; i <= r; i ++ )
            for (int j = 1; j <= c; j ++ )
                cin >> g[i][j];
        
        for (int i = 1; i <= r; i ++ )
            for (int j = 1; j <= c; j ++ )
            {
                f[i][j] = max(f[i - 1][j], f[i][j - 1]) + g[i][j];
            }
        
        cout << f[r][c] << endl;
    }
    return 0;
}

AcWing 1018. 最低通行费(曼哈顿距离-向右和向下-求最小值-初始化)

在这里插入图片描述

  • 四相邻,因此在矩阵中找任意两点的距离,用的不是欧氏距离 d = x 1 − x 2 2 + y 1 − y 2 2 d=\sqrt{{x1-x2}^2+{y1-y2}^2} d=x1x22+y1y22 ,而是曼哈顿距离 d = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ d=|x1-x2|+|y1-y2| d=x1x2+y1y2。从 ( 1 , 1 ) (1,1) (1,1) ( n , n ) (n,n) (n,n)的曼哈顿距离是 2 n − 2 2n-2 2n2,而题目要求 2 n − 1 2n-1 2n1时间内,因此,每次移动只能向右或向下,那么就是经典数字三角形。
  • 由于求最小值,需要将所有状态初始化为正无穷(这样就无法使用这些状态了),初始化状态的起点,以及状态转移越界判断。刚才那题(求最大值)就不用。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 110, INF = 1e9;

int w[N][N], f[N][N];

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            cin >> w[i][j];
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
        {
            if (i == 1 && j == 1) f[i][j] = w[i][j];        // 初始化起点
            else
            {
                f[i][j] = INF;      // 初始化
                if (i > 1) f[i][j] = min(f[i][j], f[i - 1][j] + w[i][j]);       // 不是第一行才能从上边走来
                if (j > 1) f[i][j] = min(f[i][j], f[i][j - 1] + w[i][j]);       // 不是第一列才能从左边走来
            }
        }
    
    cout << f[n][n] << endl;
    return 0;
}

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 110;

int w[N][N], f[N][N];

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            cin >> w[i][j];
    
    memset(f, 0x3f, sizeof f);
    f[1][1] = w[1][1];
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
        {
            f[i][j] = min(f[i][j], f[i - 1][j] + w[i][j]);
            f[i][j] = min(f[i][j], f[i][j - 1] + w[i][j]);
        }
    
    cout << f[n][n] << endl;
    
    return 0;
}

AcWing 1027. 方格取数

在这里插入图片描述

在这里插入图片描述

  • 只走一次时(摘花生): f [ i ] [ j ] f[i][j] f[i][j]表示所有从 ( 1 , 1 ) (1,1) (1,1)走到 ( i , j ) (i,j) (i,j)的路径的最大值, f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] + w [ i ] [ j ] , f [ i ] [ j − 1 ] + w [ i ] [ j ] ) f[i][j]=max(f[i-1][j]+w[i][j],f[i][j-1]+w[i][j]) f[i][j]=max(f[i1][j]+w[i][j],f[i][j1]+w[i][j])
  • 走两次时 :让两人同时走,题目条件转化为两条路径重合部分的值只能取一次 f [ i 1 , j 1 , i 2 , j 2 ] f[i1,j1,i2,j2] f[i1,j1,i2,j2]表示所有从 ( 1 , 1 ) , ( 1 , 1 ) (1,1),(1,1) (1,1),(1,1)分别走到 ( i 1 , j 1 ) , ( i 2 , j 2 ) (i1,j1),(i2,j2) (i1,j1),(i2,j2)的所有路径的最大值,那么只有在 i 1 + j 1 = = i 2 + j 2 i1+j1 == i2+j2 i1+j1==i2+j2时,两条路径的格子才可能重合。(其实就是同时走,所以步数是一样的,那么就可以从四维优化成三维了)
  • k k k表示两条路线当前走到的格子的横纵坐标之和, f [ k , i 1 , i 2 ] f[k,i_1,i_2] f[k,i1,i2]表示所有从 ( 1 , 1 ) , ( 1 , 1 ) (1,1),(1,1) (1,1),(1,1)分别走到 ( i 1 , k − i 1 ) , ( i 2 , k − i 2 ) (i_1,k-i_1),(i_2,k-i_2) (i1,ki1),(i2,ki2)的路径的最大值。 k k k相当于就是阶段。
  • f [ k , i 1 , i 2 ] f[k,i_1,i_2] f[k,i1,i2]按照最后一步时两条路线分别怎么走划分成四类
  • “第一条下,第二条下” :两条路线都分别两部来求。第一条路线中从 ( 1 , 1 ) (1,1) (1,1)走到 ( i 1 − 1 , k − i 1 ) (i_1-1,k-i_1) (i11,ki1)有多条路径,而最后一步从 ( i 1 − 1. k − i 1 ) (i_1-1.k-i_1) (i11.ki1)走到 ( i 1 , k − i 1 ) (i_1,k-i_1) (i1,ki1)只有一条路径,要求最大值的话,先求前面的最大值,再加上最后一步的值,前面这个最大值恰好就是状态 f ( k − 1 , i 1 − 1 , i 2 − 1 ) f(k-1,i_1-1,i_2-1) f(k1,i11,i21),后面一步要判断一下 ( i 1 − 1 , k − i 1 ) , ( i 2 − 1 , k − i 2 ) (i_1-1,k-i_1),(i_2-1,k-i_2) (i11,ki1),(i21,ki2)是不是同一个格子,如果是同一格子…
  • 同理,后三类也会有个最大值,最后总的最大值是这四个取个max
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 15;

int w[N][N];
int f[N * 2][N][N];

int main()
{
    int n;
    cin >> n;
    
    int a, b, c;
    while (cin >> a >> b >> c, a || b || c) w[a][b] = c;
    
    for (int k = 2; k <= n + n; k ++ )
        for (int i1 = 1; i1 <= n; i1 ++ )
            for (int i2 = 1; i2 <= n; i2 ++ )
            {
                int j1 = k - i1, j2 = k - i2;
                if (j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
                {
                    int t = w[i1][j1];
                    if (i1 != i2) t += w[i2][j2];
                    int &x = f[k][i1][i2];
                    x = max(x, f[k - 1][i1 - 1][i2 - 1] + t);
                    x = max(x, f[k - 1][i1 - 1][i2] + t);
                    x = max(x, f[k - 1][i1][i2] + t);
                    x = max(x, f[k - 1][i1][i2 - 1] + t);
                }
            }
    
    cout << f[2 * n][n][n] << endl;
    
    return 0;
}

AcWing 275. 传纸条

在这里插入图片描述

  • 从右下角走到左上角就等价于从左上角走到右下角。那么同上题,仅有细节改动(这里两个人不能走相同格子,注意除了起点和终点)。

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 55;

int w[N][N];
int f[N * 2][N][N];

int main()
{
    int n, m;
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            cin >> w[i][j];
    
    for (int k = 2; k <= n + m; k ++ )
        for (int i = max(1, k - m); i <= n && i < k; i ++ )
            for (int j = max(1, k - m); j <= n && j < k; j ++ )
            {
                int t = w[i][k - i];
                if (i != j || k == 2 || k == n + m)
                {
                    t += w[j][k - j];
                    for (int a = -1; a <= 0; a ++ )
                        for (int b = -1; b <= 0; b ++ )
                        {
                            f[k][i][j] = max(f[k][i][j], f[k - 1][i + a][j + b] + t);
                        }
                }
            }
    
    cout << f[n + m][n][n] << endl;
    
    return 0;
}

最长上升子序列模型

AcWing 1017. 怪盗基德的滑翔翼

在这里插入图片描述

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

using namespace std;

const int N = 110;

int n;
int h[N];
int f[N];

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ ) scanf("%d", &h[i]);

        int res = 0;
        for (int i = 0; i < n; i ++ )
        {
            f[i] = 1;
            for (int j = 0; j < i; j ++ )
                if (h[i] < h[j])
                    f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }

        memset(f, 0, sizeof f);
        for (int i = n - 1; i >= 0; i -- )
        {
            f[i] = 1;
            for (int j = n - 1; j > i; j -- )
                if (h[i] < h[j])
                    f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }

        printf("%d\n", res);
    }

    return 0;
}

AcWing 1014. 登山

在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
int h[N];
int f[N], g[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &h[i]);

    for (int i = 0; i < n; i ++ )
    {
        f[i] = 1;
        for (int j = 0; j < i; j ++ )
            if (h[i] > h[j])
                f[i] = max(f[i], f[j] + 1);
    }

    for (int i = n - 1; i >= 0; i -- )
    {
        g[i] = 1;
        for (int j = n - 1; j > i; j -- )
            if (h[i] > h[j])
                g[i] = max(g[i], g[j] + 1);
    }

    int res = 0;
    for (int i = 0; i < n; i ++ ) res = max(res, f[i] + g[i] - 1);

    printf("%d\n", res);

    return 0;
}

AcWing 482. 合唱队形

在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
int h[N];
int f[N], g[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &h[i]);

    for (int i = 0; i < n; i ++ )
    {
        f[i] = 1;
        for (int j = 0; j < i; j ++ )
            if (h[i] > h[j])
                f[i] = max(f[i], f[j] + 1);
    }

    for (int i = n - 1; i >= 0; i -- )
    {
        g[i] = 1;
        for (int j = n - 1; j > i; j -- )
            if (h[i] > h[j])
                g[i] = max(g[i], g[j] + 1);
    }

    int res = 0;
    for (int i = 0; i < n; i ++ ) res = max(res, f[i] + g[i] - 1);

    printf("%d\n", n - res);

    return 0;
}


AcWing 1012. 友好城市

在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

typedef pair<int, int> PII;

const int N = 5010;

int n;
PII city[N];
int f[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d%d", &city[i].first, &city[i].second);
    sort(city, city + n);

    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        f[i] = 1;
        for (int j = 0; j < i; j ++ )
            if (city[i].second > city[j].second)
                f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
    }

    printf("%d\n", res);

    return 0;
}

AcWing 1016. 最大上升子序列和

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1010;

int w[N], f[N];

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

AcWing 1010. 拦截导弹(贪心+stringstream语法)

在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • q数组存每个子序列结尾的数。每次放进一个数都去找结尾大于等于它的最小的数,会发现,这样的话,q数组是单调递增的
    在这里插入图片描述
  • 且发现与前面最长上升子序列问题的贪心解法一致,因此最长上升子序列的方案数等于用最少非上升子序列覆盖整个序列的方案数。
  • 不过这道题的时间复杂度卡在LIS( O ( n 2 ) O(n^2) O(n2)),因此用了二分复杂度还是这样高,没有实际意义,就不写二分了。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1010;

int n;
int w[N], f[N], q[N];		// q数组记录每个子序列末尾的值

int main()
{
    string line;
    getline(cin, line);
    stringstream sin(line);        // #include <sstream>!!
    while (sin >> w[n]) n ++ ;
    // while (cin >> w[n]) n ++ ;
    
    
    int res = 0, cnt = 0;
    for (int i = 0; i < n; i ++ )
    {
        f[i] = 1;
        for (int j = 0; j < i; j ++ )
            if (w[i] <= w[j])
                f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
        
        // 在单调递增的序列中找大于等于w[i]的最小的数
        int k = 0;
        while (k < cnt && q[k] < w[i]) k ++ ;		// 下标合法 && 找大于等于w[i]的数 
        if (k == cnt) q[cnt ++ ] = w[i];		// 下标不合法
        else q[k] = w[i];
    }
    
    cout << res << endl << cnt << endl;
    
    return 0;
}

AcWing 187. 导弹防御系统(dfs求最小步数)

在这里插入图片描述
在这里插入图片描述

  • 这题相对上题有两种选择了,就是在上一题框架之外套一层爆搜,那么爆搜需要的空间大约是 2 n 2^n 2n
  • dfs求最小步数有两种方法 :1.记一个全局最小值。2.迭代加深。这题两种都可以。此处用第一种。
  • 为什么很多题不用bfs来求解呢 ?因为bfs需要的空间太大,会把每一层都存下来,每一层有指数级别空间,而dfs只存一个路径,是线性路径。且bfs不太好剪枝,dfs是搜索树,搜的时候如果发现它的分支不太好就直接叉掉了一整棵树。这道题状态也不好用bfs。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 55;

int n;
int q[N];
int up[N], down[N];
int ans;

void dfs(int u, int su, int sd)
{
    if (su + sd >= ans) return ;
    if (u == n)
    {
        ans = su + sd;
        return ;
    }
    
    // 情况1:将当前数放入上升子序列
    int k = 0;
    while (k < su && up[k] >= q[u]) k ++ ;
    int t = up[k];
    up[k] = q[u];
    if (k >= su) dfs(u + 1, su + 1, sd);
    else dfs(u + 1, su, sd);
    up[k] = t;      // 回溯
    
    // 情况2:将当前数放入下降子序列
    k = 0;
    while (k < sd && down[k] <= q[u]) k ++ ;
    t = down[k];
    down[k] = q[u];
    if (k >= sd) dfs(u + 1, su, sd + 1);
    else dfs(u + 1, su, sd);
    down[k] = t;
}

int main()
{
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i ++ ) cin >> q[i];
        
        ans = n;        // 注意初始化!!
        dfs(0, 0, 0);
        
        cout << ans << endl;
    }
}

AcWing 272. 最长公共上升子序列

在这里插入图片描述
在这里插入图片描述

  • f [ i , j ] f[i,j] f[i,j]表示所有在 A [ 1 , i ] , B [ 1 , j ] A[1,i],B[1,j] A[1,i],B[1,j]中都出现过,且以 B [ j ] B[j] B[j]结尾的所有公共上升子序列的集合,属性是 m a x max max
  • 按照 A [ i ] A[i] A[i]是否包含在公共子序列中分为两个集合,“不包含”的集合显然为 f [ i − 1 , j ] f[i-1,j] f[i1,j](所有在 A [ i − 1 ] , B [ j ] A[i-1],B[j] A[i1],B[j]中出现,且以 B [ j ] B[j] B[j]结尾的所有…),“包含”则分为这个公共子序列中倒数第二个是什么,分成若干类 :第一类:不存在倒数第二个数,即公共上升子序列长度是1;第二类 :倒数第二个数是 B [ 1 ] B[1] B[1];第三类 :倒数第二个数是 B [ 2 ] B[2] B[2];…;倒数第二个数是 B [ j − 1 ] B[j-1] B[j1]。即一共j种可能,分为j个子集,要想求右边这个的最大值就是求这j个子集的 m a x max max
  • 但这样的朴素做法是 O ( N 3 ) O(N^3) O(N3),无法通过。dp的优化需要根据代码等价变形,因此我们先写出朴素写法。
// TLE
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 3e3 + 10;

int a[N], b[N];
int f[N][N];

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ ) cin >> b[i];
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
        {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j])
            {
                int maxv = 1;
                for (int k = 1; k < j; k ++ )
                    if (b[k] < b[j])
                        maxv = max(maxv, f[i - 1][k] + 1);
                f[i][j] = max(f[i][j], maxv);
            }
        }
    
    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
    
    cout << res << endl;
    return 0;
}

在这里插入图片描述

  • 优化 :b[j] == a[i],因此转化为a[i],这个循环就是求在b[1]…b[j-1]中所有小于a[i]的元素中,它所对应的f[i-1,k]的最大值再加1。这样的话,在求b[j+1]时会再把前面这个循环循环一遍,重新看一遍对应的f的最大值
  • 因此我们用maxv直接存前缀中打对勾的所对应的最大值即可,在求b[j+1]时就不需要重新枚举了,直接判断b[j+1]即可
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#include <sstream>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 3e3 + 10;

int a[N], b[N];
int f[N][N];

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ ) cin >> b[i];
    
    for (int i = 1; i <= n; i ++ )
    {
        int maxv = 1;
        for (int j = 1; j <= n; j ++ )
        {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
            if (b[j] < a[i]) maxv = max(maxv, f[i - 1][j] + 1);		// 只有在b[j]<a[i]的情况下才会被打对勾,才可能用对应的f更新maxv
        }
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
    
    cout << res << endl;
    return 0;
}

背包模型

背包问题初始化总结

在这里插入图片描述
总的来说就是 :求方案数, f [ 0 ] = 1 f[0]=1 f[0]=1;求最大/最小值 : f [ 0 ] = 0 f[0]=0 f[0]=0

一、01背包

AcWing 423. 采药

在这里插入图片描述
板子

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;

int f[N];

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

AcWing 1024. 装箱问题

在这里插入图片描述
价值和体积是同一个变量。注意题目所求为剩余。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 2e4 + 10;

int f[N];

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

AcWing 1022. 宠物小精灵之收服(二维体积)

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1010;

int f[N][N];

int main()
{
    int V1, V2, n;
    cin >> V1 >> V2 >> n;
    for (int i = 0; i < n; i ++ )
    {
        int v1, v2;
        cin >> v1 >> v2;
        for (int j = V1; j >= v1; j -- )
            for (int k = V2 - 1; k >= v2; k -- )
                f[j][k] = max(f[j][k], f[j - v1][k - v2] + 1);
    }
    
    cout << f[V1][V2 - 1] << " ";
    for (int i = 0; i < V2; i ++ )
        if (f[V1][V2 - 1] == f[V1][i])
        {
            cout << V2 - i << endl;
            break;
        }
    
    return 0;
}

AcWing 278. 数字组合(体积恰好,求总方案数)

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1e4 + 10;

int f[N];

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

AcWing 8. 二维费用的背包问题(二维体积)

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 110;

int f[N][N];

int main()
{
    int n, V1, V2;
    cin >> n >> V1 >> V2;
    for (int i = 0; i < n; i ++ )
    {
        int v1, v2, w;
        cin >> v1 >> v2 >> w;
        for (int j = V1; j >= v1; j -- )
            for (int k = V2; k >= v2; k -- )
                f[j][k] = max(f[j][k], f[j - v1][k - v2] + w);
    }
    
    cout << f[V1][V2] << endl;
    return 0;
}

AcWing 1020. 潜水员(二维体积,体积至少,最小价值)

在这里插入图片描述

注意状态转移时体积为负数也是合法的。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 85;

int f[N][N];

int main()
{
    int V1, V2, n;
    cin >> V1 >> V2 >> n;
    
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    
    for (int i = 0; i < n; i ++ )
    {
        int v1, v2, w;
        cin >> v1 >> v2 >> w;
        for (int j = V1; j >= 0; j -- )
            for (int k = V2; k >= 0; k -- )
                f[j][k] = min(f[j][k], f[max(0, j - v1)][max(0, k - v2)] + w);
    }
    
    cout << f[V1][V2] << endl;
}

AcWing 426. 开心的金明

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 3e4 + 10;

int f[N];

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

AcWing 11. 背包问题求方案数(求最优选法的方案数)

在这里插入图片描述

  • g [ i ] [ j ] g[i][j] g[i][j]表示 f [ i ] [ j ] f[i][j] f[i][j]取最大值的方案数
  • 由于在讨论 f [ i ] [ j ] f[i][j] f[i][j] m a x max max时会将这个集合划分成两半,即不选 i i i更新为 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j],选 i i i更新为 f [ i − 1 ] [ j − v [ i ] ] + w [ i ] f[i-1][j-v[i]]+w[i] f[i1][jv[i]]+w[i]
  • 如果不选 i i i更大, g [ i ] [ j ] g[i][j] g[i][j]更新为 g [ i − 1 ] [ j ] g[i-1][j] g[i1][j],选 i i i更大则更新为 g [ i − 1 ] [ j − v [ i ] ] g[i-1][j-v[i]] g[i1][jv[i]],如果相等则更新为 g [ i − 1 ] [ j ] + g [ i − 1 ] [ j − v [ i ] ] g[i-1][j]+g[i-1][j-v[i]] g[i1][j]+g[i1][jv[i]]
  • g [ i ] [ j ] g[i][j] g[i][j]的优化 :这里第 i i i层也只和第 i − 1 i-1 i1层有关系,且j也只和更小的j有关系,所以g也可以优化成一维,所以这道题可以用一维写。
  • 注意这里 f [ i ] [ j ] f[i][j] f[i][j]需要存储为“体积恰好为j“而非至多是j,否则涉及容斥定理。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
const double pi = acos(-1);
typedef long long ll;

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

int f[N], g[N];
         
int main()
{
    memset(f, -0x3f, sizeof f);
    
    f[0] = 0;
    g[0] = 1;
    
    int n, m;
    scanf("%d%d", &n, &m);
    
    for (int i = 1; i <= n; i ++ )
    {
        int v, w;
        scanf("%d%d", &v, &w);
        for (int j = m; j >= v; j -- )
        {
          	// 两个if而不是else if
            int cnt = 0;
            int maxv = max(f[j], f[j - v] + w);
            if (f[j] == maxv) cnt += g[j];
            if (f[j - v] + w == maxv) cnt += g[j - v];
            g[j] = cnt % mod;
            f[j] = maxv;
        }
    }
    
    int res = 0, cnt = 0;
  	// 题目所求为“体积最多为”,这里状态表示的是“体积恰好为”,所以价值最大的并不一定是体积最大的
    for (int i = 1; i <= m; i ++ ) res = max(res, f[i]);
    
    for (int i = 1; i <= m; i ++ )
        if (f[i] == res)
            cnt = (cnt + g[i]) % mod;
    
    printf("%d\n", cnt);
                 
    return 0;
}


AcWing 12. 背包问题求具体方案(打印方案)

在这里插入图片描述

  • 由于求字典序最小 :能选第一个物品就必须选第一个,能选第二个就必须选第二个…即前面的物品能选则选,所有在更新时左右两个集合都可以选时,优先选择右边 f [ i − 1 ] [ j − v [ i ] ] + w [ i ] f[i-1][j-v[i]]+w[i] f[i1][jv[i]]+w[i]的集合;如果右边可以选左边不可,必然选右边;如果左边可以右边不可以,就不能取第 i i i个物品。
  • dp时需要从后往前推,那么在求方案时就可以从前往后了,通过判断 f [ i ] [ j ] f[i][j] f[i][j]的值等于左边还是右边来判断第 i i i个物品又没有选择。
  • 求方案时就不能压缩空间了,由于不压缩空间,体积从小到大循环和从大到小循环都一样。
  • 由于从后往前推, f [ i ] [ j ] f[i][j] f[i][j]是从 i + 1 i+1 i+1更新的。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1010;

int f[N][N];
int v[N], w[N];

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

AcWing 734. 能量石 (贪心)

在这里插入图片描述

  • 考虑任意两个(是从这个大的集合里任选的)相邻的能量石 i i i i + 1 i + 1 i+1,假设先吃 i i i再吃 i + 1 i + 1 i+1,且假定吃的时候两块能量石能量都大于0(其实一定满足,因为它在那个大的集合内,且那个大的集合不吃能量为0的),总共能获得的能量是 E i + E j − s i ∗ l i + 1 E_i + E_j - s_i * l_{i+1} Ei+Ejsili+1,反之就是 E i + E j − s i + 1 ∗ l i E_i + E_j - s_{i+1}*l_i Ei+Ejsi+1li。因此,只要有 s i ∗ l l + 1 < s i + 1 ∗ l i s_i*l_{l+1} < s_{i+1}*l_i sill+1<si+1li,就一定有先吃i再吃i+1更好,这个整理一下就是 s i l i < s i + 1 l i \frac{s_i}{l_i}<\frac{s_{i+1}}{l_i} lisi<lisi+1,因此将所有能量石按这个从小到大排序,一定是最优的。因此可以证明最优方案一定是在小圈(从从小到大排好序中选)里的,所以只要找小圈里的最优方案即可。
  • f [ i ] [ j ] f[i][j] f[i][j]表示只从前i块能量石中选,且总体积“恰好”是j的方案,属性是 m a x max max
  • 转移方程 f [ j ] = m a x ( f [ j ] , f [ j − s ] + e − ( j − s ) ∗ l ) f[j]=max(f[j],f[j-s]+e-(j-s)*l) f[j]=max(f[j],f[js]+e(js)l)
  • m是所有能量石时间(体积)之和。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1e4 + 10;     // N * S

int f[N];

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

int main()
{
    int _; cin >> _;
    
    for (int C = 1; C <= _; C ++ )
    {
        int n;
        cin >> n;
        
        int m = 0;
        for (int i = 0; i < n; i ++ )
        {
            cin >> stone[i].s >> stone[i].e >> stone[i].l;
            m += stone[i].s;
        }
        
        sort(stone, stone + n);
        
        memset(f, -0x3f, sizeof f);
        f[0] = 0;
        
        for (int i = 0; i < n; i ++ )
        {
            int s = stone[i].s, e = stone[i].e, l = stone[i].l;
            for (int j = m; j >= s; j -- )
                f[j] = max(f[j], f[j - s] + e - (j - s) * l);
        }
        
        int res = 0;
        for (int i = 0; i <= m; i ++ ) res = max(res, f[i]);
        cout << "Case #" << C << ": " << res << endl;
    }
}


二、完全背包

AcWing 1023. 买书(体积恰好,求总方案数)

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1010;

int f[N];
int v[] = {10, 20, 50, 100};

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

AcWing 1021. 货币系统(体积恰好,求总方案数)

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 3010;

ll f[N];

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

AcWing 532. 货币系统(体积恰好,求总方案数)

在这里插入图片描述

  • 发现有三个性质 :1. a 1 a_1 a1, … , a n a_n an一定都能被表示出来。2.在最优解中, b i bi bi一定是从 a a a中选的。3. b 1 b1 b1 , … , b m b_m bm一定不能被其他 b i b_i bi表示出来。
  • 因此,从小到大排序,判断 a i a_i ai能否被若干个 a 1 a_1 a1,…, a i + 1 a_{i + 1} ai+1表示,如果能则说明 a i a_i ai是多余的,一定不选,否则一定选。
  • 问题转化为能否用无穷个体积为 a 1 a_1 a1, … a i − 1 a_{i-1} ai1的物品恰好装满容积为 a i a_i ai的背包,转化为完全背包问题判断方案数是否为0。

在这里插入图片描述

// 这是一道线性代数问题
// 求解一个向量组的秩(最大无关向量组的向量个数)
// 但代码写起来就是一个模拟筛的过程
// 从小到大,首先查看当前数是否已经被筛掉
// 1)如果没有就将它加入到最大无关向量组中,并把它以及它和此前的硬币的线性组合都筛掉
// 2)如果有就跳过
// 即,在完全背包求方案数的过程中,统计那些初始没有方案数的物品

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 110, M = 25010;

int a[N], f[M];

int main()
{
    int _; cin >> _;
    
    while (_ -- )
    {
        int n;
        cin >> n;
        
        for (int i = 0; i < n; i ++ ) cin >> a[i];
        
        memset(f, 0, sizeof f);
        f[0] = 1;
        
        sort(a, a + n);
        
        //我们只需统计所有物品的体积是否能被其他的线性表出
        //因此背包的体积只需设置为最大的物品体积即可
        //res用来记录最大无关向量组的个数
        
        int m = a[n - 1];
        
        int res = 0;
        for (int i = 0; i < n; i ++ )
        {
            //如果当前物品体积被之前的物品组合线性筛掉了,则它是无效的
            if (f[a[i]]) continue;
            //如果没有被筛掉,则把它加入最大无关向量组
            res ++ ;
            //筛掉当前最大无关向量组能线性表示的体积
            for (int j = a[i]; j <= m; j ++ )
                f[j] += f[j - a[i]];
        }
        cout << res << endl;
    }
    
    return 0;
}

三、多重背包

AcWing 6. 多重背包问题 III

在这里插入图片描述
当求 j − v j - v jv时其实求的是前面 s s s个长度的窗口中的最大值,“前面 s s s个的最大值“,也就是滑动窗口求极值,再转化为用单调队列求解

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 2e4 + 10;

int f[N], g[N], q[N];

int main()
{
    int n, m;
    cin >> n >> m;
    
    for (int i = 0; i < n; i ++ )
    {
        int v, w, s;
        cin >> v >> w >> s;
        
        memcpy(g, f, sizeof f);
        
        for (int j = 0; j < v; j ++ )
        {
            int hh = 0, tt = -1;
            for (int k = j; k <= m; k += v)
            {
                if (hh <= tt && k - q[hh] > s * v) hh ++ ;
                if (hh <= tt) f[k] = max(f[k], g[q[hh]] + (k - q[hh]) / v * w);
                while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) /v * w) tt -- ;
                q[ ++ tt] = k;
            }
        }
    }
    
    cout << f[m] << endl;
}

AcWing 1019. 庆功会

在这里插入图片描述
3e7居然过了。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 6010;

int f[N];

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

四、分组背包

AcWing 1013. 机器分配(打印路径)

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 20;

int f[N][N];
int w[N][N];
int way[N];

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

五、混合背包

AcWing 7. 混合背包问题

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <ctime>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define lowbit(x) (x&-x)
using namespace std;
const double pi = acos(-1);
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<long, long> PLL;

const int N = 1e3 + 10;

int f[N];

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

六、有依赖的背包问题

AcWing 10. 有依赖的背包问题

在这里插入图片描述

  • f [ u ] [ j ] f[u] [j] f[u][j]表示所有从以 u u u为根的子树中选,且总体积不超过 j j j的方案
  • 每棵子树内部以所选体积来划分,也就是说每棵子树内部体积有 m + 1 m+1 m+1种选法,摆脱了 2 k 2^k 2k级别,每一类中取最大即可
  • 在递归考虑每一个节点内部的时候,就转化成了分组背包问题
  • 如何存树 :数组模拟邻接表,且使用单链表;
  • 注意这里根节点不一定是1号点。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
const double pi = acos(-1);
typedef long long ll;

const int N = 110;

int e[N], ne[N], h[N], idx;

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

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 -- )       // 循环体积
            for (int k = 0; k <= j; k ++ )      // 循环决策
                f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
    }
    
    // 将物品u加进去
//    for (int i = v[u]; i <= m; i ++ ) f[u][i] = f[u][i - v[u]] + w[u];
    for (int i = m; i >= v[u]; i -- ) f[u][i] = f[u][i - v[u]] + w[u];
    for (int i = 0; i < v[u]; i ++ ) f[u][i] = 0;
}

int main()
{
    scanf("%d%d", &n, &m);
    
    // 初始化图
    memset(h, -1, sizeof h);
    
    // 找根
    int root;
    
    for (int i = 1; i <= n; i ++ )
    {
        int p;
        scanf("%d%d%d", &v[i], &w[i], &p);
        if (p == -1) root = i;
        else add(p, i);
    }
    
    // 从根开始dfs,而非从1节点开始
    dfs(root);
    
    printf("%d\n", f[root][m]);
    
    return 0;
}


AcWing 487. 金明的预算方案

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
const double pi = acos(-1);
typedef long long ll;

typedef pair<int, int> PII;

const int N = 65, M = 32010;

int n, m;
int f[M];
PII master[N];
vector<PII> servant[N];

int main()
{
    IOS;
    
    cin >> m >> n;
    
    for (int i = 1; i <= n; i ++ )
    {
        int v, p, q;
        cin >> v >> p >> q;
        p *= v;
        if (!q) master[i] = {v, p};
        else servant[q].push_back({v, p});		// servant[q]而非i
    }
    
    for (int i = 1; i <= n; i ++ )
        for (int u = m; u >= 0; u -- )
        {
            for (int j = 0; j < 1 << servant[i].size(); j ++ )
            {
                int v = master[i].first, w = master[i].second;		// 注意定义的位置不在i层
                for (int k = 0; k < servant[i].size(); k ++ )
                    if (j >> k & 1)
                    {
                        v += servant[i][k].first;
                        w += servant[i][k].second;
                    }
                if (u >= v) f[u] = max(f[u], f[u - v] + w);
            }
        }
    
    cout << f[m] << endl;
    
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值