LIS算法(DP)——最长不上升子序列(codevs P1044 拦截导弹)

戒骄戒躁戒傲

原本对我来说,这种三岁小孩的题目是完全不需要写博客的。
咳咳,今天中午吃得很好,而这道题是我饭后的甜点,毕竟刷题之前AC一个代码还是很舒服的一件事情。
然而。。。。。。这个经典的LIS问题——拦截导弹居然活生生耗了我一个小时二十分钟!额。。。你想知道我是怎么耗的?我不是卡在第一问,而是该死的第二问把我难住了。确切的说不是难住,而是我看过某奥赛一本通里边说第二问直接贪心,而且看他给的示例代码还真jb简单 ,所以打算5行之内完成这个贪心。。。。。。
然后一直想啊想啊想啊
FAILED!!!
没法,就怪自己笨,打开别人的博客瞄了一眼,WOC原来大家都写这么长的吗。。。。。。然后猛然发现自己的愚蠢然后开始写能AC的随便多长的代码。。。。。。
最后AC

为了狠狠扇自己(同时以后再这样也可以回头看一看 ),我打算讲的详细一点

拦截导弹

题目描述 Description
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入描述 Input Description
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数)

输出描述 Output Description
输出这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

样例输入 Sample Input样例输出 Sample Output
389 207 155 300 299 170 158 656
2

时间限制: 1 s
空间限制: 128000 KB

LIS算法求解第一问

由于题目没有给出具体多少个导弹,所以需要这样的输入:

while(cin>>d[++cnt]);
cnt--;

最后cnt会多统计一个,减去一就是总的导弹数目。

既然每一次拦截的高度不允许大于上一次,其本质就是在序列389 207 155 300 299 170 158 65 中求最长不上升子序列。那么,毫无疑问使用动态规划经典中的经典——LIS算法。
建立一个数组f,记 f[i]第1个导弹到达第i个的最大拦截数,那么最初,任何一个导弹的最大拦截数就是他自己
我们使用这样的状态转移方程求出所有的f

f[i]=max(f[i],f[j]+1);

代码如下:

for(int i=2;i<=cnt;i++){
		for(int j=1;j<=i-1;j++){
			if(d[i]<=d[j]){f[i]=max(f[i],f[j]+1);}
		}
	}
	for(int i=1;i<=cnt;i++){tot=max(tot,f[i]);}

倘若d[i]<=d[j] ,代表当前i 指向的导弹比i 前面的其中一个j 导弹矮,那么可以考虑到底是保留你当前的最大,还是说把自己的值更新为它的值加一呢?加一是因为你除了算上j 位置以前的,还要算上自己。最后的循环是为了找出所有的f[i]中的最大。
拿着笔模拟一遍你就懂了。

贪心算法求解第二问

do{
		int p=1;
		while(fm[p]) p++;
		if(p>cnt) break;
		ans++;
		for(int i=p;i<=cnt;i++){
			if(d[p]>=d[i]&&fm[i]==0){
				fm[i]=1;
				p=i;
			}
		}
	}while(1);

这个无需多言,首先使ans++ ,表示你已经使用了一套系统,那么这个系统拦截了哪些导弹呢?且看下文分解。
然后用fm 数组标记所有被选过的导弹,然后一开始没有选择一个,也就一个都不标记。我们要做的是每次从第一个没有被标记的导弹开始向后遍历出一个下降序列(不一定是最长的)。
具体怎么遍历呢?你从第一个没有被标记的导弹开始(即i=p) ,往后只要发现一个导弹比你现在小,那就标记那个导弹,然后把p挪到你标记的那个导弹上 。最后你会得到一个下降的序列。
最后,循环结束条件就是所有导弹都被标记了,也就是标记p 大于了导弹总数。
所有代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxm=50;
int d[maxm],f[maxm],cnt,tot,ans;
bool flag,fm[maxm];

int main(){
	std::ios::sync_with_stdio(false);
	while(cin>>d[++cnt]);
	cnt--;
	for(int i=1;i<=cnt;i++) f[i]=1;
	for(int i=2;i<=cnt;i++){
		for(int j=1;j<=i-1;j++){
			if(d[i]<=d[j]){f[i]=max(f[i],f[j]+1);}
//			else{f[i]=max(f[i],f[j]);}
		}
	}
	for(int i=1;i<=cnt;i++){tot=max(tot,f[i]);}
	
	
	do{
		int p=1;
		while(fm[p]) p++;
		if(p>cnt) break;
		ans++;
		for(int i=p;i<=cnt;i++){
			if(d[p]>=d[i]&&fm[i]==0){
				fm[i]=1;
				p=i;
			}
		}
	}while(1);
	
	cout<<tot<<endl<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值