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开始好像方便很多