C++知识点总结(44):线性动态规划

一、意义

动态规划(dynamic programming),将一个目标大问题“大事化小,小事化了”,分成很多的子问题,得出子问题的解后得到目标大问题的解。动态规划相当于地狱难度的递推。

问题P
子问题P1
子问题P1的解
问题P的解
子问题P2
子问题P2的解
子问题P3
子问题P3的解
子问题P4
子问题P4的解

二、例题

1. 最大子段和

1.1 审题

题目描述

给出数组 a [ ] a[] a[] 的值,如果我们取连续且非空的一段,那么这段的和最大是多少?

输入描述

1 1 1 行,是一个正整数 n n n,数组 a a a 的长度。
2 2 2 行,用空格隔开的 n n n 个整数,依次是 a [ 1 ] , a [ 2 ] , ⋯ , a [ n ] a[1],a[2],⋯,a[n] a[1],a[2],,a[n] 的值。

输出描述

1 1 1 个整数,为所求的最大的和。

样例1

输入

6
1 -6 5 -4 2 4

输出

7

提示

【样例解释】
[ 5 , − 4 , 2 , 4 ] [5,−4,2,4] [5,4,2,4] 可以得到最大的和 7 7 7


【数据范围】
对于 60 % 60\% 60% 的数据, n ≤ 100 n≤100 n100
对于 80 % 80\% 80% 的数据, n ≤ 5000 n≤5000 n5000
对于 100 % 100\% 100% 的数据, n ≤ 100000 n≤100000 n100000 1 ≤ i ≤ n 1≤i≤n 1in − 10000 ≤ a [ i ] ≤ 10000 −10000≤a[i]≤10000 10000a[i]10000,且数组 a [ ] a[] a[] 中至少有 1 1 1 个正数

1.2 思路

一开始的时候我们考虑尺取,但是右指针没有终止条件了,因此是不可以的。

接下来考虑动态规划。我们一起列举所有最大的子段和( n = 4 n=4 n=4):

ans[1]ans[2]ans[3]ans[4]ans[x]
a[1]a[2]a[3]a[4]a[x]
a[1]+a[2]a[2]+a[3]a[3]+a[4]a[x-1]+a[x]
a[1]+a[2]+a[3]a[2]+a[3]+a[4]a[x-2]+a[x-1]+a[x]
a[1]+a[2]+a[3]+a[4]a[x-3]+a[x-2]+a[x-1]+a[x]
a[1]max(a[2], a[1]+a[2])max(a[3], ans[2]+a[3])max(a[4], ans[3]+a[4])max(a[x], ans[x-1]+a[x])

不难发现,在 ans[3] 列的 a[2] 和 a[1]+a[2] 已经被计算过最大值了,那么直接用算过的最大值加上 a[3] 就可以了。所以我们推出了递推公式(即状态转移方程):
a n s [ i ] = { a n s [ i ] = a [ 1 ] i = = 1 a n s [ i ] = m a x ( a [ i ] , a n s [ i − 1 ] + a [ i ] ) i > = 1 ans[i] = \begin{cases} \text ans[i]=a[1] & i==1 \\ \text ans[i]=max(a[i], ans[i-1]+a[i]) & i>=1 \end{cases} ans[i]={ans[i]=a[1]ans[i]=max(a[i],ans[i1]+a[i])i==1i>=1
这不就能做了嘛(ps,一般在动态规划里面 ans[]dp[])。

1.3 参考答案

#include <iostream>
using namespace std;

int n;
int a[100005];
int dp[100005];
int maxn = -1e9;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    
    dp[1] = a[1];
    for (int i = 2; i <= n; i++)
    {
        dp[i] = max(a[i], dp[i-1]+a[i]);
        maxn = max(maxn, dp[i]);
    }
    cout << maxn;
    return 0;
}

2. 路径数量

2.1 审题

题目描述

一个机器人位于一个 m × n m \times n m×n 网格的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。问,总共有多少条不同的路径?(我是不会告诉你 BFS 和 DFS 也阔以滴

输入描述

输入共一行, 2 2 2 个整数分别表示 m m m n n n

输出描述

输出不同路径的数量。

样例1

输入

3 2

输出

3

样例2

输入

3 7

输出

28

提示

【样例解释1】
从左上角开始,总共有 3 3 3 条路径可以到达右下角。

  1. 向右->向下->向下
  2. 向下->向下->向右
  3. 向下->向右->向下

【数据说明】
1 ≤ m , n ≤ 100 1 \le m, n \le 100 1m,n100
题目数据保证答案 ≤ 2 × 1 0 9 \le2 \times 10^9 2×109

2.2 思路

还是要把递推公式求出来:
d p [ i ] [ j ] = { d p [ i ] [ j ] = 1 i = = 1 ∣ ∣ j = = 1 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] i > = 1 & & j > = 1 dp[i][j] = \begin{cases} \text dp[i][j]=1 & i==1||j==1 \\ \text dp[i][j]=dp[i-1][j]+dp[i][j-1] & i>=1\& \& j>=1 \end{cases} dp[i][j]={dp[i][j]=1dp[i][j]=dp[i1][j]+dp[i][j1]i==1∣∣j==1i>=1&&j>=1
这道题目我们在之前的知识点总结已经刷过了 * 遍了。

2.3 参考答案

#include <iostream>
using namespace std;

int m, n;
int dp[105][105];

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

3. 摘花生

3.1 审题

题目描述

小红想摘点花生送给她喜欢的米老鼠。她来到一片有网格状道路的矩形花生地,从西北角进去,东南角出来。地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。可多只能向东或向南走,不能向西或向北走。问可多最多能够摘到多少颗花生。

输入描述

第一行是一个整数 T T T,代表一共有多少组数据( 1 ≤ T ≤ 100 1≤T≤100 1T100)。接下来是T组数据,每组数据的第一行是两个整数,分别代表花生苗的行数 R R R 和列数 C C C 1 ≤ R , C ≤ 100 1≤R,C≤100 1R,C100)。每组数据的接下来 R R R 行数据,从北向南依次描述每行花生苗的情况。每行数据有 C C C 个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目 M M M 0 ≤ M ≤ 1000 0≤M≤1000 0M1000)。

输出描述

对每组输入数据,输出一行,内容为可多能摘到得最多的花生颗数。

样例1

输入

2
2 2
1 1
3 4
2 3
2 3 4
1 6 5

输出

8
16

3.2 思路

递推公式:
d p [ i ] [ j ] = { d p [ i ] [ j ] = 1 i = = 1 & & j = = 1 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + a [ i ] [ j ] i > = 1 & & j > = 1 dp[i][j] = \begin{cases} \text dp[i][j]=1 & i==1\& \& j==1 \\ \text dp[i][j]=max(dp[i-1][j], dp[i][j-1])+a[i][j] & i>=1\& \& j>=1 \end{cases} dp[i][j]={dp[i][j]=1dp[i][j]=max(dp[i1][j],dp[i][j1])+a[i][j]i==1&&j==1i>=1&&j>=1

3.3 参考答案

#include <iostream>
using namespace std;

int T;
int n, m;
int a[105][105];
int dp[105][105];

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

4. 金字塔路径

4.1 审题

题目描述

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到下方的点也可以到达右下方的点。

输入描述

输入有 n + 1 n+1 n+1
第一行是一个整数 n n n,表示行的数目
接下来是 n n n 行数字塔主体,数字塔的第 k k k 行有 k k k 个数字。

输出描述

第一行,包含那个可能得到的最大的和。
第二行,表示这个数塔从最顶层到最底层的行走路径。最优路径越左越好。
每个数字和箭头之间用一个空格分开。

样例1

输入

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

输出

30
7 -> 3 -> 8 -> 7 -> 5

提示

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1000 1 ≤ n ≤ 1000 1n1000,所有数塔的数据输入值在 [ 0 , 100 ] [0,100] [0,100] 范围内。

4.2 参考答案

#include <iostream>
using namespace std;

int n;
int a[1005][1005];
int dp[1005][1005];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            cin >> a[i][j];
    
    // 需求1
    for (int j = 1; j <= n; j++) // 基础项
        dp[n][j] = a[n][j];
    for (int i = n-1; i >= 1; i--)
        for (int j = 1; j <= i; j++)
            dp[i][j] = max(dp[i+1][j], dp[i+1][j+1])+a[i][j];
    cout << dp[1][1] << endl;
    
    // 需求2
    int x = 1, y = 1;
    cout << a[x][y];
    for (int i = 1; i < n; i++)
    {
        if (dp[x+1][y] >= dp[x+1][y+1]) // 最优路线优先靠左!!
        {
            x++;
        }
        else
        {
            x++;
            y++;
        }
        cout << " -> " << a[x][y];
    }
    return 0;
}

5. 最大正方形

5.1 审题

题目描述

在一个 n × m n\times m n×m 的只包含 0 0 0 1 1 1 的矩阵里找出一个不包含 0 0 0 的最大正方形,输出边长。

输入描述

第一行为两个整数 n , m n,m n,m 1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000),接下来 n n n 行,每行 m m m 个数字,用空格隔开, 0 0 0 1 1 1

输出描述

一个整数,最大正方形的边长。

样例1

输入

4 4
0 1 1 1
1 1 1 0
0 1 1 0
1 1 0 1

输出

2

5.2 参考答案

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

int n, m, maxn;
int a[1005][1005];
int dp[1005][1005];

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            if (a[i][j])
            {
                dp[i][j] = min({dp[i][j-1], dp[i-1][j], dp[i-1][j-1]})+1;
                maxn = max(maxn, dp[i][j]);
            }
        }
    
    cout << maxn;
    return 0;
}	
  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值