ACM学习总结之动态规划
这两周我学习了动态规划(dp),对动态规划有了初步的了解。
动态规划一般用来求解最优化问题。
动态规划的步骤:1.定义数组元素的含义,如定义数组dp[i]的含义,这个数组的元素代表了所求问题中各元素或阶段的状态。
定义的数组通常为一维数组和二维数组。
2.建立状态转移方程,即找出各状态的递推公式,找出各元素之间的关系,这是动态规划的核心,也是最难的部分。
3.找出初始值。这是状态转移方程得以不断递推的初始条件。
一些简单的动态规划可以看做递归。
题目
1.吃金币游戏
小明写了一个简单的吃金币游戏,规则如下:
在一个长方形地图上,玩家每次能从一个方格走到相邻一个方格。
玩家控制的角色可以向下或者向右走,但不能向上或向左走。每个方格上都有一定的金币。
现在,小明想请你帮他想一个策略,尽可能多的获得金币(从左上角走到右下角可能获得的最大金币数)。
分析:开头先输入每个点的金币值coin[i][j];
按以上所述步骤,先定义一个二维数组dp[i][j],表示到达(i,j)点时所能获取的最大金币数。因为只能向右走或向下走,因此每到达一个点都可能是从上边下来的或是从左边过来的,对于一个点所能获得的最大金币数,就是这个点的左边和上方那个点所能获得的最大金币数的较大者再加上这个点的金币数,除去第一个点,每个点都适合以上规律。故得到状态转移方程
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+coin[i][j];初始值dp[0][0]=coin[0][0];
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
using namespace std;
typedef long long ll;
int main()
{
int dp[101][101],c,m,n;
int i,j;
cin>>m>>n;
for(i=1; i<=m; i++)
for(j=1; j<=n; j++)
{
cin>>c;
dp[i][j]=max(dp[i][j-1],dp[i-1][j])+c;//只有两个方向,要么向下,要么向右
}
cout<<dp[m][n];
return 0;
}
2.最大连续子序列
给定K个整数的序列{ N1, N2, …, NK },其任意连续子序列可表示为{ Ni, Ni+1, …,
Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个,
例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和
为20。
在今年的数据结构考卷中,要求编写程序得到最大和,现在增加一个要求,即还需要输出该
子序列的第一个和最后一个元素。
分析:设数列中的各数为a[i]。若不考虑输出第一个元素和最后一个元素,可设数组b[i]为以a[i]i为结尾的最大连续子序列的值,则b[i]有两种情况,一是b[i]可能只有一个值,即b[i]=a[i],二是b[i-1]+a[i]两者取较大值,即可得状态转移方程b[i]=max(b[i-1]+a[i],a[i])。求以各元素为结尾的最大连续子序列的值,然后再去最大值,即可得出结果。但此题要求输出求得序列的首元素和末元素。末元素好求,末元素的下标即最大b[i]的下标i,那么首元素呢?首元素要满足什么条件?首元素及以首元素为首的子序列都不得小于零。那么需要一个数组来记录各子序列的和,记录以各元素为首的各子序列的和,直到找到以这个元素为首的小于零的子序列或者到终点。记录序列的首元素和末元素,然后再将b[i]的下标与上述所求子序列的首末元素比较,若在它们两者范围内则子序列的首元素可作为所求最大连续子序列的首元素,若满足条件的首元素子序列有好几个,则取下标最小的首元素························感觉自己想的特别麻烦,好像很多部分有点多余,于是我找了下题解。
题解代码如下
#include<iostream>
using namespace std;
int sum,maxsum,lef,maxleft=1,right,maxright;
int v[10001];
int main()
{
int n;
while(cin>>n&&n!=0)
{
sum=0;
maxsum=-1;
maxright=n;
for(int i=1;i<=n;i++)
{
cin>>v[i];
}
int i;
for(lef=i=1;i<=n;i++)
{
sum+=v[i];
if(sum>maxsum)
{
maxsum=sum;
maxright=i;
maxleft=lef;
}
else if(sum<0)
{
sum=0;
lef=i+1;
}
}
if(maxsum<0)
cout<<'0'<<' '<<v[maxleft]<<' '<<v[maxright]<<endl;
else
cout<<maxsum<<' '<<v[maxleft]<<' '<<v[maxright]<<endl;
}
}
题解是输入个元素后,从第一个元素开始递加求和,开始以总序列首元素为所求序列的收元素,若所求和小于零,则重置首元素,重置值为当前元素下标的下一位。递加求和的过程中不断记录最大值,若递加的和大于当前最大值,则用递加的和重置最大值。这个思路要比我的简单的多。
3.数字三角形
给定一个由 n行数字组成的数字三角形。试设计一个算法,计算出从三角形 的顶至底的一条路径(每一步可沿左斜线向下或右斜线向下),使该路径经过的数字总和最大。
分析:此题与上述题目一类似。先输入各位置元素a[i][j];设置数组dp[i][j]表示到第i行j列元素所能得到的最大数字,则可得状态转移方程 dp[i][j]=a[i][j]+max(dp[i-1][j-1],dp[i-1][j]),初始值为dp[1][1]=a[1][1].
代码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 110
int dp[MAXN][MAXN],a[MAXN][MAXN],n;
int main()
{
ios::sync_with_stdio(false);
cin>>n;
int i,j;
for(i=1;i<=n;i++){
for(j=1;j<=i;j++){
cin>>a[i][j];
dp[1][1]=a[1][1];
dp[i][j]=a[i][j]+max(dp[i-1][j-1],dp[i-1][j]);
}
}
int max=0;
for(i=n,j=1;j<=n;j++)
{
if(dp[i][j]>max)
max=dp[i][j];
}
cout<<max<<endl;
}
收获
1.定义较大的变量要放在主函数外面。
2.数据控制在十的九次方内不超时。
感悟
经过两周的学习,对动态规划有了初步的了解 。
目前只会做一些简单的题目,很多题目都不知道怎么找状态转移方程 ,课件上的题上课老师讲完后,还需课下慢慢消化。
望自己不断进步!