动态规划提高

数字三角模型

摘花生

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

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int a[N][N];

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

最低通行费

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
int f[N][N];
int a[N][N];
int n;
//这道题可以上下左右移动,但是他要求最小值
//显然到一个点后如果再往上或者往左走这不可能会是最小值
//所以只需要考虑向右走,然后就变成那个摘花生的题目了
int main()
{
    cin >> n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin >> a[i][j];
        }
    }
    //求最小值的时候一定要初始化!
    memset(f,0x3f,sizeof f);
    f[1][1] = a[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][j-1]+a[i][j]);
            f[i][j] = min(f[i][j],f[i-1][j]+a[i][j]);
        }
    }
    cout << f[n][n] << endl;
    return 0;
}

方格取数

在这里插入图片描述
在这里插入图片描述
我们主要是首先假设1号和2号同时出发,每个人每次只能走一步,那么在任意时刻,1号和2号走过的路径长度是相同的所以必定有 i1 + j1 = i2 + j2
在这里插入图片描述
在这里插入图片描述

#include <iostream>

using namespace std;

const int N = 15, M = 2 * N;

int n;
int a, b, c;
int w[N][N];
int f[M][N][N];

int main()
{
    //input
    cin >> n;
    while (cin >> a >> b >> c, a || b || c) w[a][b] += c;
    //dp
    for (int k = 2; k <= 2 * n; ++ k)
    {
        for (int i = 1; i <= n; ++ i)
        {
            for (int j = 1; j <= n; ++ j)
            {
                int &t = f[k][i][j];
                //越界判断
                if (k - i <= 0 || k - i > n || k - j <= 0 || k - j > n) continue;
                //判断是否两条路线走到了相同的格子
                int v = w[i][k - i];
                if (i != j) v += w[j][k - j];//如果两条路线走到一个格子中,则只累加一次物品的价值
                t = max(t, f[k - 1][i - 1][j - 1]);
                t = max(t, f[k - 1][i][j - 1]);
                t = max(t, f[k - 1][i - 1][j]);
                t = max(t, f[k - 1][i][j]);
                t += v;
            }
        }
    }
    //output
    cout << f[2 * n][n][n] << endl;
    return 0;
}


最长上升子序列问题

怪盗基德的滑翔翼

在这里插入图片描述
这个题与最长上升子序列唯一的不同就是基德可以向左右两个方向移动
所以不仅要求最长上升子序列还要求最长下降子序列

#include <algorithm>
#include <iostream>
using namespace std;
const int N = 110;
int f[N]; //记录最长上升子序列
int f2[N]; //记录最长下降子序列

int main()
{
    int k;
    cin >> k;
    while(k--)
    {
        int n;
        cin >> n;
        int a[N];
        for(int i=1;i<=n;i++) cin >> a[i];
        //求最长上升子序列
        for(int i=1;i<=n;i++){
            f[i] = 1;
            for(int j=1;j<i;j++){
                if(a[j] < a[i]) f[i] = max(f[i],f[j]+1);
            }
        }
        
        
        //求最长下降子序列
        for(int i=1;i<=n;i++){
            f2[i] = 1;
            for(int j=1;j<i;j++){
                if(a[j] > a[i]) f2[i] = max(f2[i],f2[j]+1);
            }
        }
        int res1 = 0;
        for(int i=1;i<=n;i++) res1 = max(res1,f[i]);
        int res2 = 0;
        for(int i=1;i<=n;i++) res2 = max(res2,f2[i]);
        cout << max(res1,res2) << endl;
        
    }
    return 0;
}

登山

在这里插入图片描述
在这里插入图片描述
即求所有这种形状的子序列
先求以 ak结尾的 从1 到 ak 的最长上升子序列
再求以 ak结尾的 从n 到 ak 的最长上升子序列即可

#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;
int n;
int a[N];
int f[N],g[N];

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

合唱队形

在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int a[N];
int f[N],g[N];

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

友好城市

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因变量就相当于上升子序列数组 a[N] 中的 i
自变量就相当于是 a[i] 我们要找a[N]中的一个上升子序列

#include <algorithm>
#include <iostream>
using namespace std;
const int N = 5010;
int f[N];
pair<int,int> a[N];

int main()
{
    int n;
    cin >> n;
    for(int i=0;i<n;i++) cin >> a[i].first >> a[i].second;
    //pair中是先排first再排second
    sort(a,a+n);
    f[0] = 1;
    for(int i=1;i<n;i++){
        f[i] = 1;
        for(int j=0;j<i;j++){
            if(a[j].second < a[i].second) f[i] = max(f[i],f[j]+1);
        }
    }
    int res = -1;
    for(int i=0;i<n;i++) res = max(res,f[i]);
    cout << res << endl;
    return 0;
}

最大上升子序列和

在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int f[N];
int a[N];

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

最长公共子序列

最长公共子序列

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

#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1010;
int n,m;
char a[N],b[N];
int f[N][N];

int main()
{
    cin >> n >> m >> a+1 >> b+1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j] = max(f[i-1][j],f[i][j-1]);//这两种情况肯定有所以先写上
            if(a[i]==b[j]) f[i][j] = max(f[i][j],f[i-1][j-1]+1);//这一种情况可能有
        }
    cout << f[n][m] << endl;
    return 0;
}

最长公共上升子序列

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

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3010;
int n;
int a[N],b[N];
int f[N][N];

int main()
{
    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 = -1;
    for(int i=1;i<=n;i++) res = max(res,f[n][i]);
    cout << res << endl;
    return 0;
}

最佳彩色带PAT1045

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

#include <iostream>

using namespace std;

const int N = 210, M = 10010;

int n, m, l;
int p[N], s[M];
int f[N][M];

int main()
{
    cin >> n;

    cin >> m;
    for (int i = 1; i <= m; i ++ ) cin >> p[i];

    cin >> l;
    for (int i = 1; i <= l; i ++ ) cin >> s[i];

    for (int i = 1; i <= m; i ++ )
        for (int j = 1; j <= l; j ++ )
        {
            f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            if (p[i] == s[j]) f[i][j] = max(f[i][j], f[i][j - 1] + 1);
        }

    cout << f[m][l] << endl;

    return 0;
}


背包模型

采药

在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int t,m;
int f[N][N];
int the_time[N],value[N];
//f[i][j]表示前i个物品中选总时间不超过j的最大价值方案

int main()
{
    cin >> t >> m;
    for(int i=1;i<=m;i++) cin >> the_time[i] >> value[i];
    
    for(int i=1;i<=m;i++){
        for(int j=0;j<=t;j++){
            f[i][j] = f[i-1][j];
            if(j>=the_time[i]) f[i][j] = max(f[i][j],f[i-1][j-the_time[i]]+value[i]);
        }
    }
    cout << f[m][t];
    return 0;
}

装箱问题

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

#include <algorithm>
#include <iostream>
using namespace std;
const int N = 40;
const int M = 20010;
int v,n;
int f[N][M],w[N];
//f[i][j]表示从前i个物品中选,总体积不超过j的最大重量方案

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

宠物小精灵收复

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

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int m,k,n;
int ball[N],blood[N];
int f[N][N];
//f[i][j][k]表示从前i个精灵中消费物品1不超过j,消耗物品2不超过k的最大方案数

int main()
{
    cin >> m >> k >> n;
    for(int i=1;i<=n;i++) cin >> ball[i] >> blood[i];
    
    //目标是f[n][m][k-1],血量不能为0所以是k-1
    for(int i=1;i<=n;i++){
        for(int j=m;j>=ball[i];j--){
            for(int t=k-1;t>=blood[i];t--){
                f[j][t] = max(f[j][t], f[j - ball[i]][t - blood[i]] + 1);
            }
        }
    }
    cout << f[m][k-1] << " ";
    int blood_cost;
    for(int i=0; i<=k-1; i++){
        if(f[m][i]==f[m][k-1]){
            blood_cost = i;
            break;
        }
    }
    cout << k - blood_cost << endl;
    return 0;
}

数字组合

在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
const int M = 10010;
int f[N][M];  //f[i][j]表示从前i个数中选出来总值恰好等于j的最大方案数
int n,m;
int a[N];

int main()
{
    cin >> n >> m;
    for(int i=1;i<=n;i++) cin >> a[i];
    
    f[0][0] = 1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            f[i][j] += f[i-1][j]; //不选第i个数
            if(j>=a[i]) f[i][j] += f[i-1][j - a[i]];
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

买书

在这里插入图片描述

#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1010;
int n;
int f[N][N];  //表示从前i个物品中选,价值不超过j的方案数
int a[5] = {0,10,20,50,100};

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

货币系统

在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3010;
long long a[N];
long long  n,m;
long long  f[N][N];

int main()
{
    cin >> n >> m;
    for(long long i=1;i<=n;i++) cin >> a[i];
    

    f[0][0] = 1;
    for(long long i=1;i<=n;i++){
        for(long long j=0;j<=m;j++){
            f[i][j] = f[i-1][j];  //这里要写等于不要写 +=
            if(j >= a[i]) f[i][j] += f[i][j - a[i]];
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

货币系统2

在这里插入图片描述
设一开始给定的货币系统是 (n,a) 也就是 a1,a2,…,an
一个与之等价的 最 优 解 货 币 系 统 ( m , b ) 也 就 是 b 1 , b 2 , . . . , b m 最优解货币系统 (m,b) 也就是 b1,b2,...,bm (m,b)b1,b2,...,bm
并且m ≤ \leq n
显然有以下性质:

  1. 那么首先一定有 a i = b 1 ∗ t 1 + b 2 ∗ t 2 + . . . + b m ∗ t m ai = b1 * t1 + b2 * t2 + ... + bm * tm ai=b1t1+b2t2+...+bmtm
    也就是说 a [ n ] a[n] a[n]中的每一个数都能够被b[n]中的数线性组合出来
  2. 并且 b 1 , b 2 , . . . , b m b1,b2,...,bm b1,b2,...,bm 一定是 a 1 , a 2 , . . . , a n a1,a2,...,an a1,a2,...,an中的一个子集
    证明: 假设b1,b2,…,bm是一个能够与a[n]等价的并且其中钞票种类最少的货币系统
    设bi ∉ \notin / a[n],但是显然 b i b_i bi ∈ \in a [ n ] 所 构 成 的 数 值 空 间 a[n]所构成的数值空间 a[n]
    那么就有 b i b_i bi = a 1 a_1 a1 t 1 t_1 t1 + a 2 a_2 a2 t 2 t_2 t2 + … + a n a_n an t n t_n tn
    而b[n]随形成的数值空间与a[n]所形成的数值空间是等价的
    那么 b i b_i bi = b 1 b_1 b1 s 1 s_1 s1 + b 2 b_2 b2 s 2 s_2 s2 + … + b n b_n bn s n s_n sn
    从而bi就可以被b[n]中的其他元素线性表示
    那么 b1,b2,…,bn就不是一个极大的与(n,a)等价的货币系统
    综上所述: b 1 , b 2 , . . . , b m b1,b2,...,bm b1,b2,...,bm一定是 a 1 , . . . , a n a1,...,an a1,...,an的子集
  3. b 1 , b 2 , . . . , b m b1,b2,...,bm b1,b2,...,bm一定不能被其他bi表示出来

如 此 问 题 转 化 为 在 a 1 , . . . , a n 中 寻 找 一 个 极 大 无 关 组 如此问题转化为在a1,...,an中寻找一个极大无关组 a1,...,an
我们先把a[n]排个序,因为大的数只可能是由小的数线性组合出来的
排完序后,我们就对每个 a i a_i ai看看能否由 a 1 a_1 a1, a 2 a_2 a2,…, a i − 1 a_{i-1} ai1组合出来

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

using namespace std;

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

const int N = 110, M = 25010;
int n;
int v[N];
bool f[M];

int main()
{
    int T = 1;
    cin >> T;
    while (T -- )
    {
        cin >> n;
        for (int i = 1; i <= n; ++ i) cin >> v[i];
        sort(v + 1, v + n + 1);//排序的原因见之前的分析

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

庆功会

在这里插入图片描述

#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510;
const int M = 6100;
int n,m;
int v[N],w[N],s[N];
int f[N][M];

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++){
            f[i][j] = f[i-1][j];
            for(int k=1;k<=s[i];k++){
                if(j>=k*v[i]) f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

混合背包问题

在这里插入图片描述

朴素做法
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1010;
int n,m;
int f[N][N];
int v[N],w[N],s[N];

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++){
            f[i][j] = f[i-1][j];
            if(s[i]==0){
                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]);
            }
            else{
                for(int k=0;k<=s[i];k++){
                    if(j>=k*v[i]) f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
                }
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

二维费用的背包问题

在这里插入图片描述

在这里插入图片描述

#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;

const int N = 1010;
int f[N][N];
int V[N],M[N],W[N];
int n,v,m;

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

潜水员

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010;
int k;  //气缸的个数
int n,m; //氧气与氮气的需要数量
int oxygen[N],nitrogen[N],weight[N];
int f[N][N];

int main()
{
    cin >> n >> m;
    cin >> k;
    for(int i=1;i<=k;i++) cin >> oxygen[i] >> nitrogen[i] >> weight[i];
    memset(f,0x3f,sizeof f);
    f[0][0] = 0;
    for(int i=1;i<=k;i++){
        for(int a=n;a>=0;a--){
            for(int b=m;b>=0;b--){//这里我们把所有j,k小于0的初始状态都合并到f[0][0][0]中来转移,也就是下面的max操作
                f[a][b] = min(f[a][b],f[max(a - oxygen[i],0)][max(b - nitrogen[i],0)]+weight[i]);
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
    
}

分组背包模板题

在这里插入图片描述

/*
f[i][j]表示从前i组中选物品总体积不超过j的最大重量的集合
分成选第i组的物品和不选第i组的物品两种情况
每个组里的每个物品只能选一次,需要枚举每个组的每个物品
*/
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int f[N][N];
int v[N][N],w[N][N];
int s[N]; //第i组的物品数量
int n,m;  //物品组数和背包容量
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=0;j<=m;j++){
            f[i][j] = f[i-1][j]; //不选第i组中的物品
            for(int k=1;k<=s[i];k++){
                if(j>=v[i][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;
}

机器分配(分组背包)

在这里插入图片描述
这个问题可以抽象成分组背包问题
把每个公司看做是一个物品组i,每个物品组里有M种物品,j号物品的体积是v[ i ][ j ] = j(把消耗的机器数抽象成体积)
价值是 w[ i ][ j ]
f[i][j]表示从前i组里面选总体积不超过j的最大方案
最终结果是 f[n][m]

#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 20;
int v[N][N],w[N][N];
int n,m;  //公司的数量以及机器台数(抽象成背包总体积)
int f[N][N];
int way[N];

int main()
{
    cin >> n >> m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin >> w[i][j];
            v[i][j] = 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=1;k<=m;k++){
                if(j>=v[i][k]) f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
            }
        }
    }
    cout << f[n][m] << endl;
    
    //way[i]记录了i号组中选了几号物品
    int j = m;
    //f[i][j]表示从前i组中选总体积不超过j的方案数目
    for (int i = n; i>=1; i -- )  //遍历每个分组
        for (int k = 0; k <= j; k ++ ) 
            if (f[i][j] == f[i - 1][j - v[i][k]] + w[i][k])
            {
                way[i] = k;
                j -= v[i][k];
                break;
            }

    for (int i = 1; i <= n; i ++ ) cout << i << ' ' << way[i] << endl;


    return 0;
}

开心的金明

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 30010;
const int M = 30;
int f[M][N];
int n,m;
int v[M],p[M];//价格和重要程度

int main()
{
    cin >> n >> m;
    for(int i=1;i<=m;i++){
        cin >> v[i] >> p[i];
    }
    for(int i=1;i<=m;i++){
        for(int j=0;j<=n;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]]+v[i]*p[i]);
        }
    }
    cout << f[m][n] << endl;
    return 0;
}

有依赖的背包问题

在这里插入图片描述
f[ u ][ j ]: 所有从以u为根的子树中选且总体积不超过j的方案集合

在这里插入图片描述
也就是说对于一个根节点为u的树,他的子树有x1,x2,…(x1,x2…均为子树的根节点)
我们为了求出根节点这个树所对应的值,进行集合划分时,不能按照传统的选法即x1选或不选,x2选或不选… …, 因为这样就会有2^k中组合出来需要遍历肯定会超时
那么现在进行一种全新的思路,对于节点u的子树,我们根据根据可以分给每个子树的体积进行搜索,如上图所示

我们可以把有依赖的背包问题看成是分组背包问题,每一个结点是看成是分组背包问题中的一个组,子节点的每一种选择我们都看作是组内的一种物品,因此我们可以通过分组背包的思想去写。
但它的难点在于如何去遍历子节点的每一种选择,即组内的物品,我们的做法是从叶子结点开始往根节点做,并使用数组表示的邻接表来存贮每个结点的父子关系。

在这里插入图片描述

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110;

int n, m, root;
int h[N], e[N], ne[N], idx;
int v[N], w[N];
int f[N][N];


void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
    //先枚举所有状态体积小于等于j-v[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)   //枚举该子节点在体积j下能使用的所有可能体积数
            {
                f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
            }
        }
    }
    //最后选上第u件物品
    for (int j = m; j >= v[u]; -- j) f[u][j] = f[u][j - v[u]] + w[u];
    for (int j = 0; j <  v[u]; ++ j) f[u][j] = 0;   //清空没选上u的所有状态
}
int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
    {
        int p;
        cin >> v[i] >> w[i] >> p;
        if (p == -1) root = i;
        else add(p, i);
    }
    dfs(root);
    cout << f[root][m] << endl;
    return 0;
}

背包问题求方案数

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

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1010, mod = 1e9 + 7;

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

int main()
{
    cin >> n >> m;
    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 (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
        }
    }
    g[0][0] = 1;
    for (int i = 1; i <= n; ++ i)
    {
        for (int j = 0; j <= m; ++ j)
        {
            if (f[i][j] == f[i - 1][j])
                g[i][j] = (g[i][j] + g[i - 1][j]) % mod;
            if (j >= v[i] && f[i][j] == f[i - 1][j - v[i]] + w[i])
                g[i][j] = (g[i][j] + g[i - 1][j - v[i]]) % mod;
        }
    }
    int res = 0;
    for (int j = 0; j <= m; ++ j)
    {
        if (f[n][j] == f[n][m])
        {
            res = (res + g[n][j]) % mod;
        }
    }
    cout << res << endl;
    return 0;
}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

新城里的旧少年^_^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值