P1020 导弹拦截

[题目链接](https://www.luogu.org/problem/P1020)
这道导弹拦截我真的冤死了,这个输入简直毒瘤。
输入文件里没有回车符,搞得我的AC代码一次又一次TLE。
那么现在开始讲这道题的O(n^2)算法。

O(n^2)算法

这个算法其实就是没有优化的最长不下降子序列。
方程如下

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

我想这个方程不需要过多的解释,
这道题明摆着是要从[n,1]求最长不下降子序列。
第二问的方案数,可以贪心选择。
原则如下
每一次把a[i]接到大于它最小的那个序列中去,若找不到能接上去的序列就新建一个序列
代码如下

#include<bits/stdc++.h>
using namespace std;
const int ll=1000000+5;
int n;
int a[ll],f[ll];
int main(){
	n=1;
	while(cin>>a[n]){
		n++;
	}
	for(int i=1;i<=n;i++) f[i]=1;
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<i;j++){
			if(a[i]<=a[j])
				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-1<<endl;
	memset(f,0,sizeof(f));
	int ans=0;	
	for(int i=1;i<=n;i++){
		pair<int,int> x;
		x.first=50000+1;
		for(int j=1;j<=ans;j++){
			if(a[i]<=f[j]&&f[j]<x.first){
				x.first=f[j];
				x.second=j;
			}
		}
		if(x.first==50000+1) f[++ans]=a[i];
		else f[x.second]=a[i];
	}
	cout<<ans;
	return 0;
}

O(nlogn)算法

最长上升子序列的每一步的目的不就是在[1,i]找到一个
末尾值小于它的长度最长的序列。
那么我们可以建立这样一个数组
f[上升子序列长度]=末位置元素最小值。
这个数组一定是单调上升的
为什么呢
反证法
设i,j,且i<j
假设存在f[i]>f[j]
也就是说
有一个长度为j的序列中末尾值最小值f[j]
比一个长度为i的序列末尾值最小值f[i]小,
这是不可能的,如果是这样f[i]必不是最优的,完全不符合我们上面的约定
f[上升子序列长度]=末位置元素最小值。
所以f数组必定单调上升。
证毕。
怎么用
数组满足单调性了之后就好二分了,
二分查找小于a[i]的最大值,返回位置p,p+1就是以a[i]结尾的上升子序列的最大长度,
代码如下

	#include<bits/stdc++.h>
	using namespace std;
	const int ll=100000+5;
	int a[ll],f[ll];
	int find(int l,int r,int x){
		while(l<r){
			int mid=(l+r+1)>>1;
			if(f[mid]<x) r=mid-1;
			else l=mid;
		}
		return l;	
	}
	int search(int l,int r,int x){
		while(l<r){
			int mid=(l+r)>>1;
			if(f[mid]<x) l=mid+1;
			else r=mid;
		}
		return l;	
	}
	
	int main(){
		int n=1;
		while(cin>>a[n]){
			n++;
		}
		int maxx=0;
		for(int i=1;i<=n;i++){
			int place=find(0,maxx,a[i]);
			f[++place]=a[i];
			maxx=max(place,maxx);
		}
		cout<<maxx-1<<endl;
		memset(f,0,sizeof(f));
		int minn=1;
		f[1]=a[1];
		for(int i=2;i<=n;i++){
			int place=search(1,minn,a[i]);
			if(f[place]>=a[i])	
				f[place]=a[i];
			else {
				f[place+1]=a[i];
				minn=max(minn,place+1);
			}
		}
		cout<<minn;
		return 0;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值