用20题入门动态规划(c/c++)

目录

1.动态规划一般步骤
2.线性dp
3.区间dp
4.背包问题
5.树型dp(待更新)
6.dp的优化(待更新)

动态规划一般步骤

1.用动态规划求解的题目,一般都可以进行划分,这个带给我的感觉就像是递归,把大问题分解成为一个个小问题,并将小问题的答案储存在一个数组之中,逐步的获得最大问题的答案。
2.划分结束之后,对问题发展到各个阶段时所处于的状态进行选择(就是储存在数组之中的值)当然,状态的选择要满足无后效性(也就是说小问题产生的结果不能对大问题产生影响)。
3.找出大,小问题之间的联系,建立两者之间的状态转移方程。
4.找出转移的边界,边界一般要写在转移方程之前,就是说现将一开始就拥有的状态写出来,这样才能向上层问题进行转移。
5…(对此类问题的优化将继续学习)

线性dp

第一题 链接:https://www.nowcoder.com/questionTerminal/c4f777778e3040358e1e708750bb7fb9?answerType=1&f=discussion 来源:牛客网
众所周知,牛妹有很多很多粉丝,粉丝送了很多很多礼物给牛妹,牛妹的礼物摆满了地板。 地板是N×MN\times MN×M的格子,每个格子有且只有一个礼物,牛妹已知每个礼物的体积。 地板的坐标是左上角(1,1) 右下角(N, M)。 牛妹只想要从屋子左上角走到右下角,每次走一步,每步只能向下走一步或者向右走一步或者向右下走一步 每次走过一个格子,拿起(并且必须拿上)这个格子上的礼物。 牛妹想知道,她能走到最后拿起的所有礼物体积最小和是多少?

思路:这是最基本的动态规划,从下往上依次递进。首先确定边界(第一行和第一列),然后在这个基础上, 向高层依次扩展,有点类似斐波那契的dp做法。

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
int dp[Max][Max] = {0};
int A[Max][Max];
int MIN(int a,int b,int c){
    int k = min(a,b);
    int h = min(k,c);
    return h;
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i<=n;++i){
        for(int j = 1;j<=m;++j){
            scanf("%d",&A[i][j]);
        }
    } 
    for(int i = 1;i<=n;++i){
        dp[i][1] = A[i][1]+dp[i-1][1];
    }
    for(int j= 1;j<=m;++j){
        dp[1][j] = A[1][j]+dp[1][j-1];
    }
    for(int i=2;i<=n;++i){
        for(int j = 2;j<=m;++j){
            dp[i][j] = MIN(dp[i-1][j-1],dp[i-1][j],dp[i][j-1]) + A[i][j];
        }
    }
    printf("%d",dp[n][m]);

    return 0;
}

2.第二题:
最长递增子串的扩展 众所周知,牛妹是一个offer收割姬,这次面试她遇到了这样的一个问题。 给了一个序列,让找出最长的“凸子序列” 何为“凸子序列”:数列中有一个xi,使得所有x0<x1<x2….xi-1<xi且xi>xi+1>xi+1>….>xn eg:12345431,是山峰序列,12345234不是山峰序列 注:单调递增或单调递减序列也算山峰序列;单独一个数是长度为1的山峰序列
思路:用两个dp数组分别从前往后求最长的递增子串与从后往前求。最后将两个dp数组相加减一取得的最大值就是结果。

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
int dp[Max],dp2[Max];
int A[Max];
int main(){
    fill(dp,dp+Max,1);
    fill(dp2,dp2+Max,1);
    int n;
    scanf("%d",&n);
    for(int i = 0;i<n;++i){
        scanf("%d",&A[i]);
    }
    for(int i = 0;i<n;++i){
        for(int j = 0;j<i;++j){
            if(A[i]>A[j]){
                dp[i] = max(dp[i],dp[j]+1);
            }
        }
    }
    for(int i = n-1;i>=0;--i){
        for(int j = n-1;j>i;--j){
            if(A[i]>A[j]){
                dp2[i] = max(dp2[i],dp2[j]+1);
            }
        }
    }
    int ans = 1;
    for(int i = 0;i<n;++i){
        ans = max(ans,dp[i]+dp2[i]-1);

    }
    printf("%d",ans);
    return 0;
}

3.第三题:生产口罩
https://www.nowcoder.com/questionTerminal/f0ebe2aa29da4a4a9d0ea71357cf2f91?answerType=1&f=discussion 来源:牛客网
牛妹是一家口罩厂家的老板,由于现在疫情严重,牛妹想重新分配每条生产线上的人数来使得能生产的口罩最多。 牛妹所在的公司一共有mmm名员工,nnn条生产线(0…n-1),每条生产线有strategy[i].size种人数安排策略。例如:333个人在aaa生产线上,aaa生产线每天生产888个口罩;555个人在aaa生产线上,每天aaa生产线能生产151515个口罩。 牛妹想知道通过合理的策略安排最多每天能生产多少口罩?(可以不用将所有员工都分配上岗,生产线可以选择闲置)

思路:dp[i][j],i表示生产线,j表示选的人数。在向上变化的过程中,一直用strategy[i]中的策略来更新不同的dp[i][j],最后 记得在第二层循环结束之后与dp[i-1][j]比较大小,因为有可能不选第i条线

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
int dp[Max][Max];
struct node{
    int x;
    int y;
};
vector<vector<node>> strategy;
int main(){
    int n,m;
    fill(dp[0],dp[0]+Max*Max,0);
    scanf("%d%d",&n,&m);
    for(int i = 1;i<=n;++i){
        for(int j = 0;j<=m;++j){
            for(int k = 0;k<strategy[i-1].size();++k){
                if(strategy[i-1][k].x+j>m) continue;
                dp[i][j+strategy[i-1][k].x] = max(dp[i][j+strategy[i-1][k].x],dp[i-1][j]+strategy[i-1][k].y); 
            }
        }
        for(int j=0;j<=m;j++) dp[i][j]=max(dp[i][j],dp[i-1][j]);
    }
    printf("%d",dp[n][m]);

   
    return 0;//1060budong
}

4.第四题:
对于三个字符串A,B,C。我们称C由A和B交错组成当且仅当C包含且仅包含A,B中所有字符,且对应的顺序不改变。请编写一个高效算法,判断C串是否由A和B交错组成。
给定三个字符串A,B和C,及他们的长度。请返回一个bool值,代表C是否由A和B交错组成。保证三个串的长度均小于等于100。
测试样例:
“ABC”,3,“12C”,3,“A12BCC”,6
返回:true

思路:先确定dp的边界,也就s1与s2在相同位置上与s3相同的点,然后依次向上变化,dp[i][j]可以由dp[i-1][j]和dp[j-1][i]变化而来。任何一种为true都可能使其为true。

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
bool dp[Max][Max];
int main(){
    string s1,s2,s3;
    int m= s1.length(),n=s2.length(),l = s3.length();
    if(m+n!=l){
        dp[m][n] = false;
        printf("false\n");
        return 0;
    }
    fill(dp[0],dp[0]+Max*Max,false); 
    dp[0][0] = true;
    for(int i= 1;i<=m;++i){
        if(dp[i-1][0]==true&&s1[i-1]==s3[i-1]){
            dp[i][0] = true;
        }
    }
    for(int j = 1;j<=n;++j){
        if(dp[0][j-1]==true&&s2[j-1]==s3[j-1]){
            dp[0][j] = true;
        }
    }
    for(int i = 1;i<=m;++i){
        for(int j = 1;j<=n;++j){
            if((dp[i-1][j]==true&&s1[i-1]==s3[i+j-1])||(dp[i][j-1]==true&&s2[j-1]==s3[i+j-1])){
                dp[i][j]=true;
            }
        }
    }
    return 0;

接下来难度递增


5.第五题:
腾讯大厦有39层,你手里有两颗一抹一眼的玻璃珠。当你拿着玻璃珠在某一层往下扔的时候,一定会有两个结果,玻璃珠碎了或者没碎。大厦有个临界楼层。低于它的楼层,往下扔玻璃珠,玻璃珠不会碎,等于或高于它的楼层,扔下玻璃珠,玻璃珠一定会碎。玻璃珠碎了就不能再扔。现在让你设计一种方式,使得在该方式下,最坏的情况扔的次数比其他任何方式最坏的次数都少。也就是设计一种最有效的方式。
思路:若要我们求解从第39层扔下的最终结果,则可划分为从第九层扔下,这时候有两种结果。一是没碎,则问题变为求解1+(39-9)层的问题;二是碎了,那就从第一层开始扔,一共扔了9次,取这两个情况的最大值。将39种情况的最大值进行比较,求出最小的那个就是答案。

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
int dp[Max];
int main(){
    int maxfloor;
    for(int i = 0;i<=maxfloor;++i){
        dp[i] = i;
        for(int j = 1;j<=i;++j){
            int temp = max(j,dp[i-j]+1);
            if(temp<dp[i]){
                dp[i] = temp;
            }
        }
    } 
    return 0;
}

6.第六题:
题目链接
正则表达式求解
请实现一个函数用来匹配包括’.‘和’+‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’+'表示它前面的字符可以出现任意次(包含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a。a"和"ab+ac+a"匹配,但是与"aa.a"和"ab+a"均不匹配。
思路:
1. 要看dp[i+1][j+1]是否为true,先看s[i]和p[j]是否相等,若相等应该考虑dp[i][j]是否为true。在这里产生第一层状态转移。(因为字符串从0开始,所以s[i]相当于dp中的i+1)
2. 如果不相等,则继续看p[j]是否是“+”,如果是的话,就继续进行状态转移,此时又继续分为两种情况:第一种:“aaa"和"aaab+”,不需要+前面那个字母进行匹配。第二种又继续分为三种情况:1、aaa和a+ 2、aaa和aaa+3、aaa和aaab+ 以这三种进行转移。

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
bool dp[Max][Max] = {false};

int main(){
    string s,p;
    cin>>s>>p;
    int len = s.length();
    int len2 = p.length();
    dp[0][0] = true;
    for(int i = 1;i<len2;++i){
        if(p[i]=='*'&&dp[0][i-1]==true){
            dp[0][i+1] = true;
        }
    }
    for(int i = 0;i<len;++i){
        for(int j = 0;j<len2;++j){
            if(s[i]==p[j]||p[j]=='.'){
                dp[i+1][j+1] = dp[i][j];
            }
            if(p[j]=='*'){
                if(p[j-1]!=s[i]&&p[j-1]!='.'){
                    dp[i+1][j+1] = dp[i+1][j-1];
                }else{
                    dp[i+1][j+1] = (dp[i+1][j-1]||dp[i][j+1]||dp[i+1][j]);
                }
            }
        }
    }
    if(dp[len][len2]==true){
        printf("ok");
    }else{
        printf("notok");
    }


    return 0;
}

区间dp:

1.第一题:
众所周知,牛妹非常喜欢打游戏,在阳光明媚的一天,她在玩一个叫做打怪兽的游戏。 也许您已经知道游戏“打怪物”。如果您不知道,没关系,让我现在告诉您,森林里有那么多怪物,您就是攻击怪物并保护村庄的英雄。 为了简化问题,我们把怪物排成一行,怪物身上有很多攻击点,你有一把剑,只能切掉攻击点,当切掉一个攻击点时,怪物会分裂成两个在段中,您必须消耗与怪物一样多的能量。您的任务是将怪物切割成不包含任何攻击点。例如,您面对一个怪物,该怪物的长度为20,并且有四个攻击点在其主体上:2 5 10 18.您可以这样切割: 1.切开第一点,您使用的能量为2 + 18 = 20; 2.切第二点,您使用的能量为3 + 15 = 18; 3.切第三点,您使用的能量为5 + 10 = 15; 4.切第四点,您使用的能量是8 + 2 = 10; 您使用的总能量为:20 + 18 + 15 + 10 = 63; 但您可以采用另一种策略: 1.切第二点,您使用的能量是5 + 15 = 20; 2.切开第一点,您使用的能量为2 + 3 = 5; 3.切第三点,您使用的能量是5 + 10 = 15; 4.切第四个点,您使用的能量为8 + 2 = 10; 您使用的能量为:20 + 5 + 15 + 10 = 50; 因此您有最佳的策略来最小化需要消耗的能量。 那么问题来了,牛妹面对给定攻击点和长度的怪兽,到底最后可以用最少多少的能量打倒怪兽呢思路:这是较为典型的区间dp总共有三层循环,第一层循环表示处理的数据的长度,第二层循环表示处理数据的起点,然后在第三层将长度为len的 数据划分为两部分并加上划分而产生的消耗,不断取最小值,不断的向上堆叠。

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
int dp[Max][Max];
vector<int> A;
int sum[Max];
int main(){
    int n,m,point;
    scanf("%d%d",&n,&m);
    for(int i = 0;i<m;++i){
        scanf("%d",&point);
        A.push_back(point);
    }
    A.push_back(0);
    A.push_back(n);
    sort(A.begin(),A.end());
    sum[0] = 0;
    for(int i = 1;i<=m+1;++i){
        sum[i] = sum[i-1]+A[i];
    }
    fill(dp[0],dp[0]+Max*Max,0);
    for(int len = 2;len<=m+1;++len){
        for(int i = 1;i+len-1<=m+1;++i){
            dp[i][i+len-1] = 0x7fffffff;
            for(int k = i;k<i+len-1;++k){
                    dp[i][i+len-1] = min(dp[i][i+len-1],dp[i][k]+dp[k+1][i+len-1]+A[i+len-1]-A[i-1]);
            }
        }
    }
    printf("%d",dp[1][m+1]);

    return 0;//1060budong

2.第二题

http://poj.org/problem?id=2955(题目来源)

We give the following inductive definition of a “regular brackets” sequence:the empty sequence is a regular brackets sequence,if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, andif a and b are regular brackets sequences, then ab is a regular brackets sequence.no other sequence is a regular brackets sequenceFor instance, all of the following character sequences are regular brackets sequences:(), [], (()), ()[], ()[()]while the following character sequences are not:(, ], )(, ([)], ([(]Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.Given the initial sequence ([([]])], the longest regular brackets subsequence is [([])].

思路:要求i与j之间的最大匹配括号数目,若s[i]=s[j],则首先考虑i+1和j-1之间的最大括号数。然后将i,j拆分为i-k,k-j两个子区间,不断地更新得到的最大值。

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
int dp[Max][Max];
int main(){
    string s1;
    cin>>s1;
    memset(dp,0,sizeof(dp));

    int n = s1.length();
    for(int len= 2;len<=n;++len){
        for(int i = 0;i+len-1<=n-1;++i){
            int j = i+len-1;
            if((s1[i]=='('&&s1[j]==')')||(s1[i]=='['&&s1[j]==']')){
                dp[i][j] = dp[i+1][j-1]+2;

            }
            for(int k = i;k<j;++k){
                dp[i][j] = max(dp[i][j],dp[i][k]+dp[k][j]);
            }
        }
    }
    printf("%d",dp[0][n-1]);
    return 0;
}

背包问题:

第一题:
众所周知,牛妹需要很多衣服,有直播的时候穿的、有舞剑的时候穿的、有跳舞的时候穿的、有弹琴的时候穿的 ,等等这些衣服都是由固定大小的矩形布料裁剪而成,心灵手巧的牛妹也知道每件衣服所需要的具体矩形布料的 长和宽然而,她只有一块大的布料可供她裁剪。裁剪的时候可以随便剪那么问题来了,美腻的牛妹能最多可以做 出多少件衣服呢? 思路:类似于 背包问题的解决,将一块布除去一块布料后,还有两块的面积,这时就将大问题一分为二了,但是这 里要注意的是,布料横着和竖着摆都是选择,因此两种都要考虑。

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max = 1110;
int dp[Max][Max];
vector<int> A;
int sum[Max];
int cloth[Max][2];
int MAX(int a,int b,int c){
    int h = max(a,b);
    int ff = max(h,c);
    return ff;
}
int main(){
    fill(dp[0],dp[0]+Max*Max,0);
    int len,width,kind;
    scanf("%d%d%d",&len,&width,&kind);
    for(int i = 0;i<kind;++i){
        scanf("%d%d",&cloth[i][0],&cloth[i][1]);
    }
    for(int i = 0;i<=len;++i){
        for(int j = 0;j<=width;++j){
            for(int k = 0;k<kind;++k){
                if(i>=cloth[k][0]&&j>=cloth[k][1]){
                    dp[i][j] = MAX(dp[i-cloth[k][0]][j]+dp[cloth[k][0]][j - cloth[k][1]] + 1,dp[i][j],
                    dp[i][j - cloth[k][1]] + dp[i - cloth[k][0]][cloth[k][1]] + 1);
                }
                if(i>=cloth[k][1]&&j>=cloth[k][0]){
                    dp[i][j] = MAX(dp[i - cloth[k][1]][j] + dp[cloth[k][1]][j - cloth[k][0]] + 1,
                    dp[i][j],dp[i][j - cloth[k][0]] + dp[i - cloth[k][1]][cloth[k][0]] + 1);
                }
            }

        }
    }
    printf("%d",dp[len][width]);
    return 0;
}
发布了2 篇原创文章 · 获赞 0 · 访问量 101

猜你喜欢

转载自blog.csdn.net/weixin_44879587/article/details/105023052