动态规划(一)
动态规划是分阶段求最优值的算法。在计算过程中,可以将复杂问题按阶段划分成子问题;枚举出子问题各种可能情况,从中找出最优值;然后利用子问题的最优值求得源问题的最优解。使用动态规划必须具有最优子结构性质。
动态规划的几个概念:
阶段
状态
决策
状态转移方程
例题:
1.吃金币游戏
在一个长方形地图上,玩家每次能从一个方格走到相邻一个方格。 玩家控制的角色可以向下或者向右走,但不能向上或向左走。每个方格上都有一定的金币。现在,小明想请你帮他想一个策略,尽可能多的获得金币(从左上角走到右下角可能获得的最大金币数)。
若F[a][b]表示达到(a,b)这个点时所能吃到的最大金币数量。可以得到递推关系式: F[a][b]=max(F[a-1][b],F[a][b-1])+Coin[a][b]则代码如下:
#include<iostream>
using namespace std;
#define MAX 1000
int f[MAX][MAX],c;
int main()
{
int m,n;
cin>>m>>n;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
cin>>c;
f[i][j]=max(f[i][j-1],f[i-1][j])+c;
}
cout<<f[m][n];
return 0;
}
2.最长上升子序列
一个数的序列bi,当b1 < b2 < … < bS 的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … <iK <= N。 比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入数据:
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N 个整数,这些整数的取值范围都在0 到10000。
输出要求:
最长上升子序列的长度。
输入样例:
7
1 7 3 5 9 4 8
输出样例:
4
分析:
子问题:求以ak(k=1, 2, 3…N)为终点的最长上升子序列的长度
状态:序列中数的位置k
k 对应的“值”,就是以ak 做为“终点”的最长上升子序列的长度。
设MaxLen (k)表示以ak 做为“终点”的最长上升子序列的长度:
MaxLen (1) = 1
MaxLen (k) = Max { MaxLen (i):1<i < k 且 ai < ak 且 k≠1 } + 1
#include<iostream>
using namespace std;
#define MAX_N 10000
int b[MAX_N+10];
int MaxLen[MAX_N+10];
int main()
{
int i,j,N;
scanf("%d",&N);
for(i=1;i<=N;i++)
scanf("%d",&b[i]);
MaxLen[1]=1;
for(i=2;i<=N;i++)
{
int nTmp=0;//记录第i 个数左边子序列最大长度
for(j=1;j<i;j++)
{
if(b[i]>b[j])
{
if(nTmp<MaxLen[j])
nTmp=MaxLen[j];
}
}
MaxLen[i]=nTmp+1;
}
int nMax=-1;
for(i=1;i<=N;i++)
if(nMax<MaxLen[i])
nMax=MaxLen[i];
printf("%d\n",nMax);
return 0;
}
3.最长公共子序列(LCS)
给出两个字符串A,B求两个的字符串的最长公共子序列。
解决方案:
用DP[i][j]代表 A串从起点到i位置的字串 和 B串从起点到j位置的字串的最长公共子序列
当A[i] == B[j] 时 :DP[i][j] = DP[i-1][j-1] + 1
当A[i] != B[j] 时 : DP[i][j] = MAX(DP[i-1][j] , DP[i][j-1])
其中,部分函数为:
int LCS()
{
int dp[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(A[i]==B[j])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
return dp[n][m];
}
4.最大子段和问题
给定 N (1 <= N <= 100000) 个绝对值不大于 1000 的整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[N],求从该序列中取出连续一个子段,使这个子段的和最大。如果某子序列全是负数则定义该子段和为0,求max{0,a[i]+a[i+1]+…+a[j]}, 1 <= i <= j <= N
输入:
有一个正整数 N,后面紧跟 N 个绝对值不大于 1000 的整数
输出:
最大子段和
样例输入:
5 6 -1 5 4 -7
样例输出:
14
分析:
dp[i] (1 <= i <= N) 表示以 a[i] 结尾的最大连续子段和。
状态转移方程:
dp[i] = max{a[i],0} (i = 1)
dp[i] = max{dp[i-1] + a[i], 0} (2 <= i <= N)
#include<iostream>
using namespace std;
#define SIZE 100000
int N;
int a[SIZE];
int dp[SIZE];
int main()
{
int N;
cin>>N;
for(int i=1;i<=N;i++)
cin>>a[i];
int maxSubSum=0;
dp[1] = max(a[1], 0);
int i;
for(i=2;i<=N;++i)
dp[i]=max(dp[i-1]+a[i],0);
for(i=1;i<=N;++i)
{
if (maxSubSum < dp[i])
maxSubSum = dp[i];
}
cout<<maxSubSum;
}
5.最大子矩阵和
给定 N (1 <= N <= 100) 和一个 N * N 的矩阵,求该矩阵的一个子矩阵,使得子矩阵中所有数字的和最大。求出这个最大值。
输入:
一个整数 N,然后是 N * N 个绝对值不超过 128 的整数
输出:只有一个值,最大子矩阵和
样例输入:
4
0 -2 -7 0 9 2 -6 2
-4 1 -4 1 -1
8 0 -2
样例输出:
15
分析:
每次枚举子矩阵最上的行 u 和最下的行 d,再把这个子矩阵每一列的值相加,压缩成一个一维的数组,对这个数组求其最大子段和。
#include<iostream>
#include<string.h>
#define N 103
int fun(int b[N],int n)
{
int i,max,c;
c=0;
max=0;
for(i=1;i<=n;i++)
{
if(c>0)
c=c+b[i];
else c=b[i];
if(max<c)
max=c;
}
return max;
}
int main()
{
int i,j,n,max,sum,k;
int a[N][N],b[N];
scanf("%d",&n);
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&a[i][j]);
max=0;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
b[j]=0;
for(j=i;j<=n;j++)
{
for(k=1;k<=n;k++)
b[k]+=a[j][k];
sum=fun(b,n);
if(max<sum)
max=sum;
}
}
printf("%d\n",max);
return 0;
}
以上是动态规划里最常见的几种题目类型,掌握其中的技巧,在我看来是有些困难却又非常重要的,要想有所提高,无非就是多练多思考,所以在平常我应该多拿出时间去练。