DP入门之最长降序子列

                                     想看更多的解题报告: http://blog.csdn.net/wangjian8006/article/details/7870410
                                     转载请注明出处:
http://blog.csdn.net/wangjian8006

拦截导弹


    某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:
    虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
    某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹。
    拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹。

    输入有两行,
    第一行,输入雷达捕捉到的敌国导弹的数量k(k<=25),
    第二行,输入k个正整数,表示k枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。
    输出只有一行,包含一个整数,表示最多能拦截多少枚导弹。

我们拿300 207 155 300 299 170 158 65举例

用a[1..i]表示第i个导弹的高度,用f[1..i]表示第i个导弹最多能打f[i]个导弹,即第i个的最长降序子列。那么我们从最后到第一个开始分析,可以肯定的是,最后一个的最长降序子列肯定是1,即自己本身,那么第二个如果比第一个高,那么就是2,否则就是1.这个很容易理解,相对于第三个,如果比第一个高,那么是f[n-2] = f[n] + 1,如果比第二个高,那么f[n-2] = f[n-1] +1,那么值最大的就是f[n-2]的值,依据这样我们可以得到一个表达式,f[i]={max(f[k])+1(i<k<=n),1},当然前提条件是a[i]>a[k],那么根据这样推算,例子的f[i]的值是,6 4 2 4 3 2 1 0

代码如下:

#include<iostream>
using namespace std;
const int MAXN =100
int n, ans= 0;
int num[MAXN+ 1];
int f[MAXN+ 1];
void solve(){
	inti, j;
	f[1] = 1;
	for (i = 2; i <= n; i++){
		f[i] = 1;
		for (j = 1; j <= i -1; j++)
			if (num[j] >= num[i] && f[j] + 1 > f[i])
			f[i] = f[j] + 1;
	}
	int ans;
	for (i = 1; i <= n; i++)
		if (f[i] > ans) ans= f[i];
	cout<< ans<< endl;
}
int main(){ 
	int i;
	cin>> n;
	for (i = 1; i <= n; i++)
		cin>> num[i];
	solve();
	return 0;
}


 

合唱队队形

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

 

          根据前面最长子序列,我的想法是,i从1到k遍历,以i为中心,算出从1到i的最长上升子序列a,i到k的最长下降子序列b,那么a+b-1就是以i为中心的剩下的最多的人数,那么从1到k选个最大的就可以了。

代码如下:

#include <iostream>
using namespace std;
#define MAXV 100

int n,a[MAXV];

int maxshangsheng(int t){
	int i,j,temp[MAXV];
	for(i=1;i<=t;i++){
		temp[i] = 1;
		for(j=1;j<=i;j++)
			if(a[j]<a[i] && temp[j]>=temp[i])
				temp[i] = temp[j] + 1;
	}
	return temp[t];
}
int maxxiajiang(int t){
	int i,j,temp[MAXV];
	for(i=n;i>=t;i--){
		temp[i] = 1;
		for(j=n;j>=i;j--)
			if(a[j]<a[i] && temp[j]>=temp[i])
				temp[i] = temp[j] + 1;
	}
	return temp[t];
}

int main(){
	int i,s,dp;
	scanf("%d",&s);
	while(s--){
		scanf("%d",&n);
		for(i=1;i<=n;i++) scanf("%d",&a[i]);
		
		int res = -1;
		for(i=1;i<=n;i++){
			dp = maxshangsheng(i) + maxxiajiang(i) - 1;
			if(dp>res) res = dp;
		}
		printf("%d\n",n - res);
	}
	return 0;
}


           但是这样的缺点是,每次都要运算一边求最长的上升与下降子序列,会重复计算,并且消耗一定的时间,那么我们可以只算一遍,就是将i从1到n开始,将i的最长上升子序列保存在a[i]中,同样将i从n到1开始,将i的最长下降子序列保存在b[i]中,那么对于i的f[i],f[i]=a[i]+b[i]-1,这样就可以节省很多时间,虽然多出了两个数组,这也是用空间换时间的一个方法。

代码如下:

#include <iostream>
using namespace std;
#define MAXV 100

int n,a[MAXV],l[MAXV],r[MAXV];

void init(){
	int i,j;
	for(i=1;i<=n;i++){
		l[i] = 1;
		for(j=1;j<=i;j++)
			if(a[j]<a[i] && l[j]>=l[i])
				l[i] = l[j] + 1;
	}

	for(i=n;i>=1;i--){
		r[i] = 1;
		for(j=n;j>=i;j--)
			if(a[j]<a[i] && r[j]>=r[i])
				r[i] = r[j] + 1;
	}
}

int main(){
	int i,s;
	scanf("%d",&s);
	while(s--){
		scanf("%d",&n);
		for(i=1;i<=n;i++) scanf("%d",&a[i]);

		init();
		
		int res = -1;
		for(i=1;i<=n;i++){
			if((l[i] + r[i] - 1)>res) res = (l[i] + r[i] - 1);
		}
		printf("%d\n",n - res);
	}
	return 0;
}


 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值