每日四题打卡-4.22:区间DP-石子合并/线性DP数字三角形/背包问题

区间DP-石子合并

设有N堆石子排成一排,其编号为1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这N堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有4堆石子分别为 1 3 5 2, 我们可以先合并1、2堆,代价为4,得到4 5 2, 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24;

如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式

第一行一个数N表示石子的堆数N。

第二行N个数,表示每堆石子的质量(均不超过1000)。

输出格式

输出一个整数,表示最小代价。

数据范围

1≤N≤3001≤N≤300

输入样例:

4
1 3 5 2

输出样例:

22

思路:假设有一堆石子,1,3,5,2,我们把1,3合并,5,2合并,总共是4 + 7 + 11 = 22.是最小代价。如下图所示:

每日四题打卡-4.22:区间DP-石子合并/线性DP数字三角形/背包问题_第1张图片

所有合并的个数,如果选取堆数有n - 1次选择,然后第二次从n- 1中选就有n -2次选择--》(n - 1)*(n - 2)*.....

状态表示f[i][j],集合:所有将i到j合并成一堆的方案的集合。(j - i)!。属性:min,集合中付出的最小代价。

状态计算:化整为零的过程,把f[i][j]分解成若干个子问题,分而治之。实际上就是从最后一步开始往前递推。

最小方案:min(f(i, k)) +min(f(k + 1, j))+从i到j的部分和s[i] - s[i - 1];

每日四题打卡-4.22:区间DP-石子合并/线性DP数字三角形/背包问题_第2张图片

#include 
#include 
using namespace std;
const int N = 510;
int n;
int s[N];//前缀和
int f[N][N];
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> s[i], s[i] += s[i - 1];//更新前缀和
    for (int len = 2; len <= n; len ++)//len从2开始,如果从1开始没有意义
        for (int i = 1; i + len - 1 <= n; i ++)//枚举区间左端点:i+ len - 1是左边端点
        {
            int j = i + len - 1;//枚举右端点
            //枚举之前
            f[i][j] = 1e8;//先将i,J初始化成一个特别大的值
            for (int k = i; k < j; k ++)//枚举k
                //式子直接抄过来
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
        }
    //把f[1][n]带入定义就是所有将1-n合并的方案最大值
    cout << f[1][n] << endl;
    return 0;
}

线性DP-数字三角形

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

输入格式

第一行包含整数n,表示数字三角形的层数。

接下来n行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。

输出格式

输出一个整数,表示最大的路径数字和。

数据范围

1≤n≤5001≤n≤500,
−10000≤三角形中的整数≤10000−10000≤三角形中的整数≤10000

输入样例:

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

输出样例:

30

思路:每一次只能走一个格子,有很多条路可以走。找到一条路径上所有数字之和最大的。

每日四题打卡-4.22:区间DP-石子合并/线性DP数字三角形/背包问题_第3张图片

首先先把他分成i 行j列,用f[i][j]表示所有起点到f[i][j]点所有路径之和的集合。比如第四行第二个点为7表示为(4, 2);从最后的终点往前递推:如图划圈的点7,所以从起点到f[i][j]点路径分成两类:一种是从左上方一种是来自右上方。

左上方:比如从7那个点到8,所以需要往上走一格即f[i - 1][j - 1] 并且得加上该点的值a[i][j];

右上方:比如画圈那个点右上方的1,跟左上方的点8在同一行,但是不同列,所以走一格即f[i - 1, j] + a[i][j];

然后不断往上递归,直到到达起点为止。最后将两种情况取max。如下图所示:

每日四题打卡-4.22:区间DP-石子合并/线性DP数字三角形/背包问题_第4张图片

注意边界问题:如果涉及到i-1的下标循环得从i = 1开始。

动态规划时间复杂度如何求:状态数量 * 转移的计算量
 

#include 
#include 
using namespace std;
const int N = 510, INF = 1e9;
int n;
int a[N][N];//表示每个点的值
int f[N][N];//存从起点到第i,j点的路径最大长度
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)//这里有问题是<=i,不是<=n
            scanf("%d", &a[i][j]);
    //初始化,这里必须注意,从0开始到n,然后每一列得多+1,因为三角形最右边有边界,求f[i][j]的时候会遍历到每列最右边的点,然后他的右上角的点实际上不存在的,所以初始化的时候必须把它初始化成INF
    for (int i = 0; i <= n; i ++)
        for (int j = 0; j <= i + 1; j ++)
            f[i][j] = -INF;//这里得是负无穷
    f[1][1] = a[1][1];//第一个点就是他本身的值
    //i从2开始
    for (int i = 2; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);//求左上角右上角那个点的最大值遍历
    //这里又一个问题,如果是最后一行,则必须得把最后一行的最大值求出来
    //最终答案是要遍历最后一行,最后一行可能会走到每一个位置,求终点的最大值,然后枚举起点到终点的最大值
    int res = -INF;//这里得是负无穷
    for (int i = 1; i <= n; i ++) res = max(res, f[n][i]);
    printf("%d\n", res);
    return 0;
}

背包问题

有 NN 件物品和一个容量是 VV 的背包。每件物品只能使用一次。

第 ii 件物品的体积是 vivi,价值是 wiwi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,VN,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0 0

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8
/*
f[i][j]:
1.不选第一个物品:f[i][j] = f[i - 1][j];
2.选第i个物品:f[i][j] = f[i - 1][j - v[i]]
f[i][j] = max(1, 2)
f[0][0] = 0;
*/
#include 
#include 
using namespace std;
const int N = 1010;
int n, m;//n表示物品个数,m表示背包容量

int v[N], w[N];//体积,价值
//暴力做法
int f[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]);
        }
    cout << f[n][m] << endl;
    return 0;
}

//优化做法:使用滚动数组来做,
//如果f(i)只用到了f(i - 1),缩到一维来做,交替来算。f(0)和f(1)交替来算
// int f[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 = m; j >= v[i]; j --)//枚举体积
//             f[j] = max(f[j], f[j - v[i]] + w[i]);//找最大的
//     cout << f[m] << endl;
//     return 0;
// }

多背包问题

有 NN 件物品和一个容量是 VV 的背包。每件物品能使用多次。

状态计算不一样了,f(i, j)分为选和不选两种问题,但是这一次可以无限用。需要划分无数个子集。先考虑朴素怎么做,然后在找优化方法。

每日四题打卡-4.22:区间DP-石子合并/线性DP数字三角形/背包问题_第5张图片

每日四题打卡-4.22:区间DP-石子合并/线性DP数字三角形/背包问题_第6张图片

推导过程:

每日四题打卡-4.22:区间DP-石子合并/线性DP数字三角形/背包问题_第7张图片

#include 
#include 
using namespace std;
const int N = 1010;
int n, m;//n表示物品个数,m表示背包容量
int v[N], w[N];//体积,价值
//朴素做法
// int f[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][j - v[i]] + w[i]);
//         }
//     cout << f[n][m] << endl;
//     return 0;
// }
 
//优化做法
int f[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 = v[i]; j <= m; j ++)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    cout << f[m] << endl;
    return 0;
}

总结

 

 

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