浅说线性DP(下)

声明

最近博主身体不适,更新较慢,请大家体谅体谅

最大上升子序列

【题目描述】
一个数的序列
你的任务,就是对于给定的序列,求出最大上升子序列和。注意,最长的上升子序列的和不一定是最大的,比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。

【输入】
输入的第一行是序列的长度N(1≤N≤1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。

【输出】
最大上升子序列和。

如果前面的最长上升子序列那道题理解清楚了,那么该题做起来就会发现本质上和前一道题是一样的。该题的决策对象和阶段都和前一道题一样,只是状态变为dp[i]表示以第i个数作为结尾的最大上升子序列的长度。
决策:这里要求子序列之和最大,对于每一个位置的数,只能从前面的比当前值小的数转移过来,要求序列之和最大,那么这个序列前面选择的数之和要最大,那么我们需要从前面可以选择的点中序列和最大的点转移过来。

我们设dp[i]表示以i结尾的最大子序列之和,那么我们就可以很轻松的得到一下内容
d p [ i ] = m a x ( d p [ i ] , d p [ j ] + a [ i ] ) j ∈ [ 1 , i − 1 ] dp[i]=max(dp[i],dp[j]+a[i]) j\in[1,i-1] dp[i]=max(dp[i],dp[j]+a[i])j[1,i1]
所以就有以下ACcode

#include<bits/stdc++.h>
using namespace std;

int a[10010],dp[10010],ans=INT_MIN;
int main(){
	int n;
	cin>>n;
	for (int i=1;i<=n;i++){
		cin>>a[i];
		dp[i]=a[i];
	}	
	for (int i=1;i<=n;i++){
		for (int j=1;j<i;j++){
			if (a[j]<a[i]){
				dp[i]=max(dp[i],dp[j]+a[i]);
			}
		}
		ans=max(ans,dp[i]);
	}
	cout<<ans;
	return 0;
}

合唱队形

我们先来看一道例题

[NOIP2004 提高组] 合唱队形

题目描述

n n n 位同学站成一排,音乐老师要请其中的 n − k n-k nk 位同学出列,使得剩下的 k k k 位同学排成合唱队形。

合唱队形是指这样的一种队形:设 k k k 位同学从左到右依次编号为 1 , 2 , 1,2, 1,2, , k ,k ,k,他们的身高分别为 t 1 , t 2 , t_1,t_2, t1,t2, , t k ,t_k ,tk,则他们的身高满足 t 1 < ⋯ < t i > t i + 1 > t_1< \cdots <t_i>t_{i+1}> t1<<ti>ti+1> > t k ( 1 ≤ i ≤ k ) >t_k(1\le i\le k) >tk(1ik)

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

输入格式

共二行。

第一行是一个整数 n n n 2 ≤ n ≤ 100 2\le n\le100 2n100),表示同学的总数。

第二行有 n n n 个整数,用空格分隔,第 i i i 个整数 t i t_i ti 130 ≤ t i ≤ 230 130\le t_i\le230 130ti230)是第 i i i 位同学的身高(厘米)。

输出格式

一个整数,最少需要几位同学出列。

样例 #1

样例输入 #1

8
186 186 150 200 160 130 197 220

样例输出 #1

4

提示

对于 50 % 50\% 50% 的数据,保证有 n ≤ 20 n \le 20 n20

对于全部的数据,保证有 n ≤ 100 n \le 100 n100

该题实际上就是前面求一个最长上升子序列,对后面求一个最长下降子序列。但是问题在于这两个序列的连接点我们不知道,没有办法直接求解出来,也就是说任意一个点都有可能作为这个连接点,所以我们需要先枚举这个连接点x,再分别对区间[1,x]求最长上升子序列,对区间[x+1,n]求最长下降子序列。这种做法的时间复杂度为O(n3),但是该题的数据范围改为n<=2000,所以还需要优化。

我们先看前面的最长上升子序列,在枚举的连接点i逐渐增大的时候,如果我们已经存储了前i-1个数的最长上升子序列的答案,那么前i个数的最长上升子序列的答案就只有两种情况,第一种就是前面i-1的答案,第二种就是以第i个点作为结尾的答案,没有必要再去前面重新计算一次,每一次的时间复杂度为O(n)。对于后面的下降序列,在i增大时,范围在逐渐缩小,我们只需要把它看做是增大就可以,这种做法在之前做过的一些题中使用过,我们只需要倒着枚举连接点i即可,这样,i在逐渐变小,后面的区间在增大,做法和前面一样。

#include<bits/stdc++.h>
using namespace std;

int n,dp1[5000],dp2[5000];//dp1为从前往后的最长上升子序列,dp2为从后往前 
int a[5000],minn=INT_MAX;
int main(){
	cin>>n;
	for (int i=1;i<=n;i++){
		cin>>a[i];
		dp1[i]=1;
		dp2[i]=1;
	}
	for (int i=1;i<=n;i++){
		for (int j=1;j<i;j++){
			if (a[j]<a[i]){
				dp1[i]=max(dp1[i],dp1[j]+1);
			}
		}
	}
	for (int i=n;i>=1;i--){
		for (int j=n;j>i;j--){
			if (a[i]>a[j]){
				dp2[i]=max(dp2[i],dp2[j]+1);
			}
		}
	}
	for (int i=n;i>=1;i--){
		minn=min(n-dp1[i]-dp2[i],minn);
	}
	
	cout<<minn+1;
	return 0;
}

最长公共子序列

我们要得到两个字符串的最长公共子序列,暴力做法是先枚举一个字符串的开头,再去枚举另一个字符串的开头,然后找出最大公共子列,时间复杂度为O(n^3)。我们考虑如何优化,前面依然是枚举两个字符串的开头,主要考虑求最大公共子序列时是否可以通过前面算出的答案直接得到正确答案。考虑开头无法知道匹配的最长公共子序列到底匹配到了哪一个位置,所以我们记录下以当前位置结尾的最长公共序列的长度。因为有两个字符串,所以dp[i]无法分别记录两个字符串的结尾位置,所以需要用dp[i][j]。

状态:我们设dp[i][j]表示前缀长度为i的a子串和一个长度为j的b序列的最长公共子序列的长度。决策对象:每个位置的字符。阶段:a串的每个位置和b串的每个位置都可以组合,一共有n*m个阶段。决策:如果a[i]==b[j],那么(i,j)相对于(i1,j-1)会多匹配一个,即dp[i][j]=dp[i-1][j-1]。如果a[i]!=b[j],答案就是dp[i-1][j-1]吗?其实并不是,因为a[i]可以和b[j-1]匹配,或者b[j]可以和a[i-1]匹配,这种情况下,dp[i-1][j-1]的答案是错误的。

当a[i]!=b[j]时,正确答案是dp[i][j]=max (dp[i-1][j],dp[i][j-1]),那么万一是a[i]和b[j1]、a[i-1]和b[j]都无法匹配的情况,是否还需要和dp[i-1][j-1]比较呢?不需要,因为在这种情况下,计算dp[i][j-1]时,a[i]!=b[j-1],那么dp[i][j-1]=max(dp[i-1][j-1], dp[i][j-2]),这个答案中已经报了dp[i-1][j-1]的情况,所以不需要再和dp[i-1][j-1]比较。还需要设置初始值,当a串和b串都没有字符时,最长公共串长度为1,即dp[0][0]=0。

#include<bits/stdc++.h>
using namespace std;

int dp[1010][1010];
int main(){
    string a,b;
    cin>>a>>b;
    dp[0][0]=0;
    for (int i=1;i<=a.length();i++){
        for (int j=1;j<=b.length();j++){
            if (a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+1;
            else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
    }
    cout<<dp[a.length()][b.length()];
    return 0;
}

最长公共子串

该题和上一题的状态、对象、阶段都是相同的,不同的是状态的转移。dp[i][j]表示以位置i结尾的a串和以位置j结尾的b串的最长公共子串. 如果a[i]!=b[j],那么这个子串一定是不可以匹配的,因为这里说了分别以i,j结尾,所以dp[i][j]=0。如果a[i]=b[j],长度就在之前的dp[i-1][j-1]的基础上增加1,即dp[i][j]=dp[i-1][j-1]+1。

#include<bits/stdc++.h>
using namespace std;

int dp[1500][1510];
int maxn=INT_MIN;
int main(){
	string a,b;
	cin>>a>>b;
	dp[0][0]=0;
	for (int i=1;i<=a.length();i++){
		for (int j=1;j<=b.length();j++){
			if (a[i-1]==b[j-1]){
				dp[i][j]=dp[i-1][j-1]+1;
			}
			maxn=max(dp[i][j],maxn);
		}
	}
	cout<<maxn;
	return 0;
}
  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值