天天看点

动态规划入门 & 线性动态规划

参考文献:全国青少年信息学竞赛培训教材——复赛(陈合力 游光辉 编著)

一、概念

在多阶段决策的问题中,各阶段采取的决策,一般俩说是与空间或者时间相关的。决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来,故有动态的含义。

我们称这种解决多阶段决策最优化的过程称为动态规划方法。

例如在一个m*n的迷宫中,从左下角走到右上角

动态规划入门 & 线性动态规划

可以看到,状态A和状态B应当属于同一个阶段。T可以从A走来,也可以从B走来,故dp[T]=min(dp[A]+map[A][T],dp[B]+map[B][T]);即终点的最短路应当是A或者B的最短路+最后一段长度.

动态规划的几个术语:

阶段:把所求问题的过程按照时间或者空间恰当地划分成若干个相互联系的阶段。

状态:表示每个阶段的客观状态,它既是每个阶段的出发位置,又是前一阶段的终点。

无后效性:如果给定某一阶段的状态,则在这一阶段以后的过程发展不受这个阶段以前的各个状态的影响,所以各阶段确定了,整个过程也就确定了。

决策:一个阶段的状态给定之后,从该状态演变到下一状态的一种选择。

策略:每个阶段决策组成的序列。

最优性原理:把一个大问题划分成多个子问题,对于整个问题必须最优的情况时,子问题也必须最优,即问题具备最优子结构的性质。

二、线性动态规划

线性动态规划中状态是一维的,第i个元素的状态与前i-1个元素的状态有关,前i-1个状态组成一个决策序列,它是其他类动态规划的基础。

最长不下降子序列

Given an unsorted array of integers, find the length of longest

increasing subsequence.

For example, Given [10, 9, 2, 5, 3, 7, 101, 18], The longest

increasing subsequence is [2, 3, 7, 101], therefore the length is 4.

Note that there may be more than one LIS combination, it is only

necessary for you to return the length.

我们用dp[i]表示序列长度为i的时候最长不下降子序列的长度。

当序列长度为i时,dp[i]=max{d[j]}+1(0<=j

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size(),dp[n];
        memset(dp,,sizeof(dp));
        dp[]=;
        for(int i=;i<n;i++){
            //求取前j个最大并且nums[j]<nums[i]的
            //这里max需要设置为0,这样就可以在没有找到最大值的情况下,也不需要设置额外的找到flag
            //使dp[i]=1
            int max=;
            for(int j=i-;j>=;j--)
            if(nums[j]<nums[i] && dp[j]>max)
            max=dp[j];

            //应用dp方程
            dp[i]=max+;
        }
        int max=;
        for(int i=;i<n;i++)
        if(dp[i]>max) max=dp[i];
        return max;
    }
};
           

合唱队形

题目描述: N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK, 则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入: 输入的第一行是一个整数N(2 <= N <= 100),表示同学的总数。 第一行有n个整数,用空格分隔,第i个整数Ti(130 <= Ti <= 230)是第i位同学的身高(厘米)。

输出: 可能包括多组测试数据,对于每组数据,输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

样例输入:

8

186 186 150 200 160 130 197 220

分别求出从左往右最长递增序列,从右往左最长递增序列(即从左往右递减)。

计算每一个位置作为中点的时候,队伍能达到的长度,取最长。

注意求从左往右递减的时候,需要从n-1开始遍历,否则不满足无后效性的条件

#include<stdio.h> 
#include<string.h>
#include<stdlib.h>
#define N 101
using namespace std;

int increase[N],decrease[N],height[N];

int main(){
    int n;
    while(scanf("%d",&n)!=EOF){

        for(int i=;i<n;i++){
            scanf("%d",&height[i]);
            increase[i]=;
            decrease[i]=;
        }

        //从左往右找递增
        for(int i=;i<n;i++) {
            for(int j=i-;j>=;j--){
                if(increase[j]+>increase[i] && height[j]<height[i]) 
                increase[i]=increase[j]+;
            }
        }

        //这样是不科学的
        //不满足无后效性,即确定了dp[i]之后,仍然有可能出现比它小的值,使d[i]的值增加 
    //  for(int i=0;i<n;i++) {
    //      int max=0;
    //      for(int j=i-1;j>=0;j--){
    //          if(decrease[j]>max && height[j]>height[i])
    //          max=decrease[j];
    //      }
    //      decrease[i]=max+1;
    //  }
        for(int i=n-;i>=;i--) {
            for(int j=i+;j<n;j++){
                if(decrease[j]+>decrease[i] && height[j]<height[i])
                decrease[i]=decrease[j]+;
            }
        }

        int max=;
        for(int i=;i<n;i++){
            if(increase[i]+decrease[i]- > max)
            max=increase[i]+decrease[i]-;
        }

        printf("%d\n",n-max);
    }
//  system("pause");
    return ;
}
           

最长公共子序列

设置dp[i][j]表示下标为0~i和0~j之间的最长公共子序列的长度。

对于这两个序列,当最后一个元素相等时,将转化为前a[0~i-1] b[0~j-1]的问题。

否则,其最长长度为dp[i-1][j] dp[i][j-1]的最大值

具备最优子结构性质。

从左往右推导,可以保证在dp[i][j]的情况下,dp[i-1][j] dp[i][j-1]均已求出。

//核心代码
for(int i=;i<lena;i++){
    for(int j=;j<lenb;j++){
        if(a[i]==b[j])
        dp[i][j]=dp[i-][j-]+;
        else dp[i][j]=max(dp[i][j-],dp[i-][j]);
    }
}
printf("%d\n",dp[lena-][lenb-]);
           

继续阅读