DP入门题总结

DP入门题总结

ps:一些DP简单入门题汇总,仅供自己复习所用,如有错误,还望指出(而且这全是入门级别题)

动态规划(dynamic programming),简称DP,这一类题目可以说是算法竞赛中最为灵活的内容之一,需要经验,有时候也需要一点灵感,如果能找到要转换的状态,题目看上去就会很简单。
动态规划程序设计是对解最优化问题的一种途径、一种方法,而不是一种特殊算法。
这类题目可以出的很难,区间DP,树形DP,数位DP,概率DP等等,这里只介绍了最简单的线性DP和二维DP。

DP中最重要的就是寻找状态转移方程

A-楼梯问题


Problem Description


有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?


Input


输入数据首先包含一个整数N,表示测试实例的个数,然后是N行数据,每行包含一个整数M(1<=M<=40),表示楼梯的级数。


Output


对于每个测试实例,请输出不同走法的数量


Sample Input


2
2
3


Sample Output


1
2

题解:这道题就是一道简单的DP题,
当求上第n级楼梯时共有多少种走法时就是第n级的状态,
而状态转移方程我们很容易知道:
f[n]=f[n-1]+f[n-2]

这题代码实现也有三种实现方法:填表法、刷表法、记忆化搜索
这里就直接采用填表法,直接预先打表(身为小白的我最喜欢的就是打表)

#include<cstdio>
#define ll long long
using namespace std;
ll f[100];
int main()
{
    for(int i=1;i<=40;i++)
    {
        if(i==1||i==2)
            f[i]=1;
        else
            f[i]=f[i-1]+f[i-2];     //状态转移方程
    }
    ll n;
    ll m;
    scanf("%lld",&n);
    while(n--)
    {
        scanf("%lld",&m);
        printf("%lld\n",f[m]);
    }
    return 0;
}

B-数塔问题

下图是个数字三角,请编写一个程序计算从顶部至底部某处一条路径,使得该路径所经过的数字总和最大。

7

3 8

8 1 0

2 7 4 4

1. 每一步可沿左斜线向下或右斜线向下走;

2. 1<=三角形行数<=100

3. 三角形中的数字为整数 0,1,……,99。

4. 如果有多种情况结果都最大,任意输出一种即可。

输入:

第一行一个整数N,代表三角形的行数。

接下来N行,描述了一个数字三角。

输出:

第一行一个整数,代表路径所经过底数字总和。

第二行N个数,代表所经过的数字。

样例:


输入:
4
7
3 8
8 1 0
2 7 4 4

输出:
25
7 3 8 7

题解:当然这道题也是一道DP水题,
于是以:f[i][j]表示从第i行第j列个元素所在位置到三角形底部所经过的数字总和的最大值

状态转移方程为:f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j]

当然这道题有两种方法,一种从顶部向底部推,一种从底部向顶部推,这里采用从底部向顶部推
于是代码如下:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int a[105][105],f[105][105];
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<=i;j++)
            scanf("%d",&a[i][j]);
    }
    for(int i=0;i<n;i++)
        f[n-1][i]=a[n-1][i];             //先将最后一行存入到f数组中
    for(int i=n-1;i>=0;i--)
    {
        for(int j=0;j<=i;j++)
            f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j];     //状态转移方程
    }
    printf("%d\n",f[0][0]);
    int num,j=0;
    printf("%d",a[0][0]);
    for(int i=1;i<n;i++)      //寻找所经过的数
    {
        num=f[i-1][j]-a[i-1][j];
        if(num==f[i][j+1])
            j++;
        printf(" %d",a[i][j]);
    }
    printf("\n");
    return 0;
}

C:0-1背包问题

ps:当然这里的0-1背包问题是最为基础的,裸的0-1背包


B - Bone Collector HDU -2602


Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?

在这里插入图片描述


Input


The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.


Output


One integer per line representing the maximum of the total value (this number will be less than 2 31).


Sample Input


1
5 10
1 2 3 4 5
5 4 3 2 1


Sample Output


14

题解:可以看出这就是一道裸的0-1背包问题,于是分析其状态以及状态转移方程
状态:
前i个物品,背包容量为j时,可以获得最大价值

状态转移方程:
面对第 i 个物品时,无非就是拿或者不拿两种选择
如果此时背包的容量 j < 该物品的重量 w[i]
装不下只能选择不拿:m[ i ][ j ] = m[ i-1 ][j]
若 j >= w[i],
选择拿,则需要腾出w[i]的空间,获得价值为 m[ i-1 ][ j-w[i] ] + v[i]
选择不拿,则和上面情况一样,m[i-1][j]
两者取最大值
即:
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];

例如:在这里插入图片描述
于是这道题代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
int value[1010],weight[1010],m[1010][1010];          
//分别表示骨头的价值,重量,以及状态转移方程    重点解释m[i][j]:i表示价值域,j表示重量域
int t,n,v;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(value,0,sizeof(value));
        memset(weight,0,sizeof(weight));
        memset(m,0,sizeof(m));
        scanf("%d%d",&n,&v);
        for(int i=1;i<=n;i++)
            scanf("%d",&value[i]);
        for(int i=1;i<=n;i++)
            scanf("%d",&weight[i]);
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=v;j++)           //从0开始,意味着重量为0也可以计算
            {
                if(j>=weight[i])
                    m[i][j]=max(m[i-1][j],m[i-1][j-weight[i]]+value[i]);          //状态转移方程
                else
                    m[i][j]=m[i-1][j];
            }
        }
        printf("%d\n",m[n][v]);
    }
    return 0;
}

D-最长上升子序列

例题: POJ:2533
Sample Input
7
1 7 3 5 9 4 8

Sample Output
4

题解:同样分析其状态以及其状态转移方程
状态:
以f[i]表示前i个数字中以前i个数字结尾所能产生的最长上升子序列
状态转移方程:
f[i]=max(f[i],f[j]+1)

于是代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<iostream>
using namespace std;
int a[1010],f[1010];
int main()
{
    int n;
    int num=0;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        f[i]=1;                //初始化
    }
    for(int i=1; i<=n; i++)
    {
        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++)
        num=max(num,f[i]);                //找最长上升子序列长度
    printf("%d\n",num);
    return 0;
}

E-最长公共子序列

例题: POJ:1458
Sample Input
abcfbc abfcab
programming contest
abcd mnp

Sample Output
4
2
0

题解:同样分析其状态与状态转移方程
状态:
以f[i][j]表示第一个字符串S1前i个字符与第二个字符串S2前j个字符所能得到的最长公共子序列
状态转移方程:
if(S1[i-1]==S2[j-1])
f[i][j]=f[i-1][j-1]+1;
else
f[i][j]=max(f[i-1][j],f[i][j-1]);
举个例子:
在这里插入图片描述
于是上面那题代码如下:

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
int f[1010][1010];
int main()
{
    ios::sync_with_stdio(false);
    string S1,S2;
    while(cin>>S1>>S2)
    {
        memset(f,0,sizeof(f));
        int len1=S1.length();
        int len2=S2.length();
        for(int i=1;i<=len1;i++)
        {
            for(int j=1;j<=len2;j++)
            {
                if(S1[i-1]==S2[j-1])
                    f[i][j]=f[i-1][j-1]+1;
                else
                    f[i][j]=max(f[i-1][j],f[i][j-1]);            //状态转移方程
            }
        }
        printf("%d\n",f[len1][len2]);
    }
    return 0;
}

以上就是一些简单的DP入门题汇总啦!!!

PS:小白发现好像DP中循环以1开始好像方便很多

发布了127 篇原创文章 · 获赞 32 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/boliu147258/article/details/89843491