动态规划问题总结

本博客根据AcwingDP问题总结:只做资料的搬运工

1、 数字三角形模型

Acwing1027. 方格取数
设有 N × N N \times N N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
Acwing2017

某人从图中的左上角 A A A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B B B 点。

在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。

此人从 A A A 点到 B B B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。

输入格式
第一行为一个整数 N N N,表示 N × N N \times N N×N 的方格图。

接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。

行和列编号从 1 1 1 开始。

一行" 000 0 0 0 000"表示结束。

输出格式
输出一个整数,表示两条路径上取得的最大的和。

数据范围
N ≤ 10 N\leq10 N10
输入样例:

8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0

输出样例:

67
#include<bits/stdc++.h>
using namespace std;
const int N  = 15;
int f[2*N][N][N];
int q[N][N];
int n;
int main(){
    scanf("%d",&n);
    int x,y,k;
    while(scanf("%d %d %d", &x, &y, &k),x) q[x][y] = k;
    for(int k = 2;k<=2*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>0&&j1<=n&&j2>0&&j2<=n){
                    int t = q[i1][j1];
                    if(i1!=i2) t+=q[i2][j2];
                    int &x = f[k][i1][i2];
                    x = max(f[k-1][i1-1][i2-1]+t,x);
                    x = max(f[k-1][i1][i2]+t,x);
                    x = max(f[k-1][i1][i2-1]+t,x);
                    x = max(f[k-1][i1-1][i2]+t,x);
                }
            }
        }
    }
    cout<<f[2*n][n][n];
    return 0;
}

参考题解

2、 序列DP模型

Acwing1017. 怪盗基德的滑翔翼
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。

而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。

有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。

不得已,怪盗基德只能操作受损的滑翔翼逃脱。

假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。

初始时,怪盗基德可以在任何一幢建筑的顶端。

他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。

因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。

他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。

请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?

输入格式
输入数据第一行是一个整数 K K K,代表有 K K K组测试数据。

每组测试数据包含两行:第一行是一个整数 N N N,代表有 N N N幢建筑。第二行包含 N N N个不同的整数,每一个对应一幢建筑的高度 h h h,按照建筑的排列顺序给出。

输出格式
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。

数据范围
1 ≤ K ≤ 100 1\leq K\leq100 1K100,
1 ≤ N ≤ 100 1\leq N\leq100 1N100,
0 < h < 10000 0 <h<10000 0<h<10000
输入样例:

3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10

输出样例:

6
6
9

解题思路:一次最长上升子序列,一次最长下降子序列


#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int k,n,h;
int q[N],f[N];
int main(){
    scanf("%d",&k);
    while(k--){
        int res = 0;
        scanf("%d",&n);
        for(int i =0;i<n;i++) scanf("%d",&q[i]);
        for(int i = 0;i<n;i++){
            f[i] =1;
            for(int j = 0;j<i;j++){
                if(q[i]>q[j]){
                    f[i] = max(f[i],f[j]+1);
                }
            }
            res = max(res,f[i]);
        }
        for(int i = n-1;i>=0;i--){
            f[i] = 1;
            for(int j=n-1;j>i;j--){
                if(q[j]<q[i]){
                    f[i] = max(f[i],f[j]+1);
                }
            }
            res = max(res,f[i]);
        }
        printf("%d\n",res);
    }
    return 0;
}

Acwing1010. 拦截导弹
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于 30000 30000 30000的正整数,导弹数不超过 1000 1000 1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式
共一行,输入导弹依次飞来的高度。

输出格式
第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围
雷达给出的高度数据是不大于 30000 30000 30000 的正整数,导弹数不超过 1000 1000 1000

输入样例:

389 207 155 300 299 170 158 65

输出样例:

6
2

解法:一次最长上升子序列,一次贪心算法

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
int f[N],q[N],s[N];
int main(){
    int x,n =0;
    while(~scanf("%d",&x)){
        q[++n] = x; 
    }
    int res = 0;
    int cnt = 0;
    for(int i =1 ;i <=n ;i++){
        f[i] = 1;
        for(int j =1 ;j<i ;j++){
            if(q[i]<=q[j]){
                f[i] =max(f[i],f[j]+1);
            }
        }
        res = max(res,f[i]);
        int k = 0 ;
        while(k<cnt && s[k] < q[i]) k++;
        if(k==cnt) s[cnt++] = q[i];
        else s[k] = q[i];
    }
    printf("%d\n",res);
    printf("%d\n",cnt);
}

待学习:AcWing 187. 导弹防御系统

Leetcode1473.粉刷房子III
在一个小城市里,有 m m m 个房子排成一排,你需要给每个房子涂上 n n n 种颜色之一(颜色编号为 1 1 1 n n n )。有的房子去年夏天已经涂过颜色了,所以这些房子不可以被重新涂色。

我们将连续相同颜色尽可能多的房子称为一个街区。(比方说 houses = [1,2,2,3,3,2,1,1],它包含 5 个街区 [{1}, {2,2}, {3,3}, {2}, {1,1}]。)

给你一个数组 h o u s e s houses houses ,一个 m ∗ n m * n mn 的矩阵 cost 和一个整数 target ,其中:

houses[i]:是第 i i i 个房子的颜色, 0 0 0 表示这个房子还没有被涂色。
cost[i][j]:是将第 i i i 个房子涂成颜色 j + 1 j+1 j+1 的花费。
请你返回房子涂色方案的最小总花费,使得每个房子都被涂色后,恰好组成 target 个街区。如果没有可用的涂色方案,请返回 − 1 -1 1

示例 1:

输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:9
解释:房子涂色方案为 [1,2,2,1,1]
此方案包含 target = 3 个街区,分别是 [{1}, {2,2}, {1,1}]。
涂色的总花费为 (1 + 1 + 1 + 1 + 5) = 9。

示例 2:

输入:houses = [0,2,1,2,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:11
解释:有的房子已经被涂色了,在此基础上涂色方案为 [2,2,1,2,2]
此方案包含 target = 3 个街区,分别是 [{2,2}, {1}, {2,2}]。
给第一个和最后一个房子涂色的花费为 (10 + 1) = 11。

示例 3:

输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[1,10],[10,1],[1,10]], m = 5, n = 2, target = 5
输出:5

示例 4:

输入:houses = [3,1,2,3], cost = [[1,1,1],[1,1,1],[1,1,1],[1,1,1]], m = 4, n = 3, target = 3
输出:-1
解释:房子已经被涂色并组成了 4 个街区,分别是 [{3},{1},{2},{3}] ,无法形成 target = 3 个街区。

提示:

m = = h o u s e s . l e n g t h = = c o s t . l e n g t h m == houses.length == cost.length m==houses.length==cost.length
n = = c o s t [ i ] . l e n g t h n == cost[i].length n==cost[i].length
1 ≤ m ≤ 100 1 \leq m \leq 100 1m100
1 ≤ n ≤ 20 1 \leq n \leq 20 1n20
1 ≤ t a r g e t ≤ m 1 \leq target \leq m 1targetm
0 ≤ h o u s e s [ i ] ≤ n 0\leq houses[i] \leq n 0houses[i]n
1 ≤ c o s t [ i ] [ j ] ≤ 1 0 4 1 \leq cost[i][j] \leq 10^4 1cost[i][j]104

解决代码

class Solution {
public:
    int minCost(vector<int>& houses, vector<vector<int>>& cost, int m, int n, int target) {
        const int INF = 0x3f3f3f3f;
        int f[105][25][105];
        // 无效的状态
        for(int i = 0 ;i<=m ; i++){
            for(int j = 0 ; j<=n ;j++){
                f[i][j][0] = INF;
            }
        }
        for(int i = 1 ;i <=m ; i++){
            int color = houses[i-1];
            for(int j = 1 ;j<=n ;j++){
                for(int k = 1;k<=target;k++){
                    //没有分区数大于房子数 
                    if(k > i){
                         f[i][j][k] = INF;
                        continue;
                    }
                    if(color!=0){
                        // 涂了某种颜色
                        if(j==color){
                            int cur = INF;
                            for(int p = 1 ;p<=n ; p++){
                                if(p!=j){
                                    cur = min(cur , f[i-1][p][k-1]);
                                }
                            }
                            f[i][j][k] = min(cur,f[i-1][j][k]);
                        }else{
                            f[i][j][k] =INF;// 无效状态
                        }
                    }else{
                        int  u = cost[i-1][j-1]; //第i个房间第j个颜色的花费
                        int cur =INF;
                        for(int p = 1 ;p<=n;p ++){
                            if(p!=j ) cur = min(cur ,f[i-1][p][k-1]+u);
                        }
                        f[i][j][k] = min(cur,f[i-1][j][k]+u);
                    }
                }
            }
        }
        int ans = INF;
        for(int i = 1 ; i<=n ;i ++){
            ans = min(ans,f[m][i][target]);
        }
        return ans == INF? -1:ans;

    }
};

参考题解

3、 区间DP模型

Acwing282. 石子合并
设有 N 堆石子排成一排,其编号为 1 , 2 , 3 , … , N 1,2,3,…,N 123N

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

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

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

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

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

输入格式
第一行一个数 N N N 表示石子的堆数 N N N

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

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

数据范围
1 ≤ N ≤ 300 1 \leq N \leq300 1N300
输入样例:

4
1 3 5 2

输出样例:

22
#include <bits/stdc++.h>

using namespace std;

const int N = 310;

int n;
int s[N];
int dp[N][N];
int f[N][N];

int main()
{
    scanf("%d",&n);
    memset( dp,0x3f ,sizeof dp);
    for(int i = 1 ;i <= n ;i ++ ) {scanf("%d",&s[i]),s[i] += s[i-1];f[i][i] = i,dp[i][i] = 0;} ;
    for(int len = 2;len <= n ;len ++ ){
        for(int l = 1 ; l + len -1 <= n ;l ++){
            int r = l + len - 1 ;//dp[i][j]
            for(int k = f[l][r-1] ; k<= f[l+1][r] ; k++){
                if(dp[l][k] + dp[k+1][r] + s[r] - s[l-1] < dp[l][r]){
                    dp[l][r] = dp[l][k] + dp[k+1][r] + s[r] - s[l-1];
                    f[l][r] = k;
                }
            }
        }
    }
    printf("%d",dp[1][n]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值