第四周总结

动态规划(一)

动态规划是分阶段求最优值的算法。在计算过程中,可以将复杂问题按阶段划分成子问题;枚举出子问题各种可能情况,从中找出最优值;然后利用子问题的最优值求得源问题的最优解。使用动态规划必须具有最优子结构性质。
动态规划的几个概念:
阶段
状态
决策
状态转移方程

例题:

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;
}

以上是动态规划里最常见的几种题目类型,掌握其中的技巧,在我看来是有些困难却又非常重要的,要想有所提高,无非就是多练多思考,所以在平常我应该多拿出时间去练。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值