最长单调子序列问题(LIS,dp入门)

dp的一种很简单的题型。
小编便以两道题来介绍一下。

问题一:防卫导弹
题目描述
一种新型的防卫导弹可截击多个攻击导弹。它可以向前或向下飞行,但不可以向后或向上飞行。它有一个缺点,尽管它发射时可以达到任意高度,但它只能截击比它上次截击导弹时所处高度低或者高度相同的导弹。现对这种新型防卫导弹进行测试,在每一次测试中,发射一系列的测试导弹,该防卫导弹所能获得的信息包括各进攻导弹的高度,以及它们的发射次序。求在每次测试中,该防卫导弹最多能截击的进攻导弹数量。

输入
第1行有若干个整数hi(0≤hi≤32767),表示进攻导弹的高度,其中导弹数不超过4000个。

输出
一个整数,表示最多能截击的进攻导弹数。

样例输入
36 25 45 17 22 28
样例输出
3

分析:

选择一个导弹,之后只能选比它高度低的导弹。中间比开始的导弹高的就会跳过或去除(不计入)。这种 在一段序列中去除一些、而得出最长的一段单调序列的问题,即是dp经典题型:最长单调子序列问题。

最长非降子序列的长度。 (讲DP基本都会讲到的一个问题LIS:longest increasing subsequence)

代码

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
int n,a[4001],f[4001];
int main(){
    
    
    while(scanf("%d",&a[++n])!=EOF)
        ;
    n--;
//  检验输入
//  for(int i=1;i<=n;i++)	cout<<a[i]<<' ';
//核心代码
    for(int i=1;i<=n;i++){
    
    
    	f[i]=1;
      	for(int j=1;j<i;j++)
        	if(a[j]>=a[i])	f[i]=max(f[i],f[j]+1);
    }
    int res=0;
    for(int i=1;i<=n;i++)	res=max(res,f[i]);
    cout<<res<<endl;
}

1、不定输入之前讲过了,略。
2、最长单调子序列问题的核心代码如下:

for(int i=1;i<=n;i++){
    
    
    f[i]=1;
    for(int j=1;j<i;j++)
        if(a[j]>=a[i])	f[i]=max(f[i],f[j]+1);
}

dp[i]即f[i]表示以i为结尾的最长单调(这里是递减)子序列。
所以以这个定义方法,要找出最长的,需要遍历dp[i];

int res=0;
for(int i=1;i<=n;i++)	res=max(res,f[i]);

问题二:合唱队形

题目描述
n位同学站成一排,音乐老师要请其中的(n-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1,T2,…,TK,则他们的身高满足T1<…Ti+l>…>TK(1≤i≤K)。你的任务是:已知所有n位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

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

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

样例输入
8
186 186 150 200 160 130 197 220
样例输出
4

题目分析:
dp,最长递增或递减子序列的变形,找到以i结尾的左侧最长递增子序列,和以i开头的右侧最长子序列,求和值最大的情况.
仔细体会dp问题中利用递推,以及状态的保存。

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
int n,a[101],dp[2][101],sum;
int main(){
    
    
	cin>>n;
	for(int i=0;i<n;i++)	cin>>a[i];
	//找出以i结尾的最长递增子序列
	for(int i=0;i<n;i++){
    
    
		dp[0][i]=1;
		for(int j=0;j<i;j++){
    
    
			if(a[i]>a[j])	dp[0][i]=max(dp[0][i],dp[0][j]+1);
		}
	}
	//找出以i开头的最长递减子序列(逆序的递增)
	for(int i=n-1;i>=0;i--){
    
    
		dp[1][i]=1;
		for(int j=n-1;j>i;j--){
    
    
			if(a[i]>a[j])	dp[1][i]=max(dp[1][i],dp[1][j]+1);
		}
	}
	for(int i=0;i<n;i++)	sum=max(sum,dp[0][i]+dp[1][i]);
	//i在dp1和dp2中算了两遍,最后要减去
	cout<<(n-sum+1)<<endl;
}

猜你喜欢

转载自blog.csdn.net/weixin_45606191/article/details/103686616