动态规划经典题目


下面来总结一下动态规划的经典题目

最大连续子序列和

Given a sequence of K integers { N1, N2, …, N**K }. A continuous subsequence is defined to be { N**i, N**i+1, …, N**j } where 1≤ijK. The Maximum Subsequence is the continuous subsequence which has the largest sum of its elements. For example, given sequence { -2, 11, -4, 13, -5, -2 }, its maximum subsequence is { 11, -4, 13 } with the largest sum being 20.

Now you are supposed to find the largest sum, together with the first and the last numbers of the maximum subsequence.

Input Specification:

Each input file contains one test case. Each case occupies two lines. The first line contains a positive integer K (≤10000). The second line contains K numbers, separated by a space.

Output Specification:

For each test case, output in one line the largest sum, together with the first and the last numbers of the maximum subsequence. The numbers must be separated by one space, but there must be no extra space at the end of a line. In case that the maximum subsequence is not unique, output the one with the smallest indices i and j (as shown by the sample case). If all the K numbers are negative, then its maximum sum is defined to be 0, and you are supposed to output the first and the last numbers of the whole sequence.

Sample Input:

10
-10 1 2 3 4 -5 -23 3 7 -21
 

Sample Output:

10 1 4

分析:

题目的意思很明确:找出给定序列中和最大的子序列。输出最大的和以及序列的第一个数和最后一个数。如果最大的和不唯一,输出下标小的序列。特殊处理:如果所有的数都是负数,则输出最大和为0,并且输出序列的第一个元素和最后一个元素

这个题可以这么想:令状态dp[i]表示以A[i]作为末尾的连续序列的最大和。这样dp[i]在i属于(1,n)的最大值救赎最大连续子序列的和。而对于dp[i]的值,只有两种情况:

  1. 这个最大和的子序列只有一个元素,以A[i]开始,以A[i]结尾
  2. 这个最大和的子序列是由前面的某处的A[p](p<i)开始,以A[i]结尾

初始的边界条件是dp[0]=A[0]。状态转移方程为dp[i] = max(A[i],dp[i-1]+A[i])

另外,关注一下如何记录最大和的开始数。(结尾数可以由i得到):用数组s[i]表示以a[i]为结尾的最大连续子序列的开始元素的下标。由上面的分析可以得到

  1. s[i] = i ;当序列只有一个元素的时候
  2. s[i] = s[i-1]

代码如下:

#include<cstdio>
const int maxn = 1000;
int dp[maxn],a[maxn]={0};
int A[maxn];
int main(){
    int n;
    scanf("%d",&n);        
    int flag = false;
    for(int i=0; i<n; i++){
        scanf("%d",&A[i]);
        if(A[i]>0) flag = true;
    }
    //对特殊情况进行处理
    if(flag == false){
        printf("%d %d",0,A[0],A[n]);
        return 0;
    }
    //边界条件的处理
    dp[0] = A[0];
    for(int i=1; i<n; i++){
        if(A[i] > dp[i-1]+A[i]){
            dp[i] = A[i];
            a[i] = i;
        }else{
            dp[i] = dp[i-1]+A[i];
            a[i] = a[i-1];
        }
    }
    int k,ans=0;
    for(int i=1; i<n; i++){
        if(dp[i]>ans){
            ans = dp[i];
            k = i;
        }
    }
    printf("%d %d %d",ans,A[a[k]],A[k]);
    
}

最长公共子序列(LCS)

问题描述为:给定两个字符串A和B,求一个字符串,使得这个字符串是A和B的最长公共部分(子序列可以不连续)。例如,字符串"sadstory"与"adminsorry"的最长公共子序列为"adsory"。长度为6。

这样来分析:设dp[i][j]表示字符串A的i号位和字符串B的j号位之前的最长功能子序列的长度。于是当A[i]==B[j]的时候,则字符串A和字符串B的LCS增加一位,当A[i] != B[j] 时 ,dp[i][j]就为A的前i位于B的前j-1位于A的前i-1位,B的前j位的LCS的最大值。边界条件为 dp[i][0]=0,dp[0][j]=0。状态转移方程如下:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1 , A [ i ] = = A [ j ] m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , A [ i ] ! = A [ j ] dp[i][j]=\begin{cases} dp[i-1][j-1]+1,A[i]==A[j]\\ max(dp[i-1][j],dp[i][j-1]),A[i]!=A[j]\end{cases} dp[i][j]={dp[i1][j1]+1,A[i]==A[j]max(dp[i1][j],dp[i][j1]),A[i]!=A[j]
代码

#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 100;
char A[maxn],B[maxn];//用来存放字符串
int dp[maxn][maxn];//dp数组,dp[i][j]表示A[i]之前(包括A[i])和B[j]之前(包括B[j])的LCS

int main() {
	gets(A+1);//从下标1开始读数据 
	gets(B+1);
	int lenA = strlen(A+1);
	int lenB = strlen(B+1);//长度从1开始 
	
	for(int i=0; i<=lenA; i++){
		dp[i][0] = 0;
	}
	for(int j=0; j<=lenB; j++){
		dp[0][j] = 0;
	} 
	for(int i=1;i<=lenA; i++){
		for(int j=1; j<=lenB; j++){
			if(A[i]==B[j]){
				dp[i][j] = dp[i-1][j-1]+1;
			}else{
				dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
			}
		}
	}
	printf("%d",dp[lenA][lenB]);
}

最长不下降子序列(LIS)

LIS是这样的一个问题:在一个数字序列中,找到一个最长的子序列(可以不连续),使得这个子序列是不下降的。例如给定一串数字:-3,1,3,-4,5,3 则最长不下降序列的长度为4(-3,1,3,5)

分析:这个和上面的最大连续子序列的思路有类似之处。设dp[i]表示以A[i]结尾的最长不下降子序列长度。这样来思考:要求以A[i]为结尾的LIS。就要考虑能否将A[i]加到 以A[1],A[2]…,A[i-1]结尾的子序列的后面。若加到后面能够使得dp[i]变大,就跟新。所以状态转移方程为 dp[i] = max(1,dp[j]+1) ; j=1,2…,i-1 且A[j] < A[i]。边界条件为dp[i] = 1。

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1000;
int A[maxn],dp[maxn];
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++){
		scanf("%d",&A[i]);
		dp[i] = 1;
	}
	int ans = -1; //记录最大的dp[i]
	for(int i=1; i<=n; i++){
		for(int j=1; j<i; j++){
			if(A[i]>=A[j] && dp[j]+1 > dp[i]){
				dp[i] = dp[j] +1;
			}
		}
		ans = max(ans,dp[i]);
	} 
	printf("%d",ans); 
}

最长回文子串

对于一个字符串,求它的最长回文子串的长度,比如给定一个字符串:abccbde。则最长回文子串的长度为4(bccb)

思路:令dp[i][j]所表示的子串是否为回文子串,是则为1,不是则为0。这样根据S[i]是否等于S[j],可以把转移情况分为两类:

  1. S[i]==S[j],那么只要S[i+1]至S[j-1]是回文子串,则S[i]至S[j]就是回文。否则就不是
  2. 如果S[i]!=S[j],那么S[i]至S[j]一定不是回文子串

状态转移方程为:
d p [ i ] [ j ] = { d p [ i + 1 ] [ j − 1 ] , S [ i ] = = S [ j ] 0 , S [ i ] ! = S [ j ] dp[i][j]=\begin{cases}dp[i+1][j-1],S[i]==S[j]\\0,S[i]!=S[j]\end{cases} dp[i][j]={dp[i+1][j1],S[i]==S[j]0,S[i]!=S[j]
边界为:

dp[i][i]==1 ,dp[i][i+1]=(S[i]==S[i+1])?1:0

这里要注意的是,可以按子串的长度和子串的初始位置进行枚举:即第一遍将长度为3的子串的dp值全部求出,第二遍通过第一遍结果计算出长度4的子串的值。

#include<cstdio>
#include<cstring>
const int maxn = 100;
char s[maxn];
int dp[maxn][maxn] ={0};
int ans = 1;
int main(){
	scanf("%s",s);
	int len = strlen(s);
	for(int i=0; i<len; i++){
		//边界条件 
		dp[i][i] = 1;
		if(i<len-1){
			if(s[i]==s[i+1]){
				dp[i][i+1] = 1;
				ans = 2;
			}	
		}
	}
	for(int L =3 ;L<=len;L++) {
		for(int i=0; i+L-1<len; i++){
			int j = i+L-1;
			if(s[i]==s[j]&&dp[i+1][j-1] == 1){
				dp[i][j]=1;
				ans = L;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值