洛谷 [P1020] 导弹拦截 (N*logN)

首先此一眼就能看出来是一个非常基础的最长不下降子序列(LIS),其朴素的 N^2做法很简单,但如何将其优化成为N*logN?
我们不妨换一个思路,维护一个f数组,f[x]表示长度为x的LIS的最大的最后一个数字是f[x]。(为什么是最大的?可以应用贪心的思想,发现对于相同的x,f[x]越大其后可能扩展的情况就越多,即就越优)我们可以发现f数组单调递减(为什么?也可使用反证法证明,在此不赘述)对于决策单调性问题,一般使用二分法优化,这就是logN的来历。二分的边界条件一定要写对。
代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
using namespace std;
int read(){
	int rv=0,fh=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') fh=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		rv=(rv<<1)+(rv<<3)+c-'0';
		c=getchar();
	}
	return fh*rv;
}//快读
int num[100005],f[100005],q[100005];
int find1(int x){
	int l=1,r=f[0],m=0;
	while(l<=r){
		m=(l+r)>>1;
		if(f[m]>=x){
			l=m+1;
		}else{
			r=m-1;
		}
	}
	//if(l<=f[0]) return l;
	if(r>=1) return r;
	return 0;
}
int find2(int x){
	int l=1,r=f[0],m;
	while(l<=r){
		m=(l+r)>>1;
		if(f[m]>=x){
			r=m-1;
		}else {
			l=m+1;
		}
	}
	if(r>=1) return r;
	return 0;
}
int main(){
	freopen("in.txt","r",stdin);
	while(scanf("%d",&num[++num[0]])==1) ;
	num[0]--;//一定要减
	f[1]=num[1];f[0]++;
	for(int i=2;i<=num[0];i++){
		int pos=find1(num[i]); 
		if(pos){
			if(f[0]==pos) f[0]++;
			f[pos+1]=max(f[pos+1],num[i]);
			//cout<<f[pos+1]<<endl;
		}else {
			f[1]=max(f[1],num[i]);
		}
	}
	cout<<f[0]<<endl;
	for(int i=1;i<=f[0];i++){
		f[i]=1000005;
	}
	f[0]=1;
	f[1]=num[1];
	for(int i=2;i<=num[0];i++){
		int pos=find2(num[i]);
		if(pos){
			if(f[0]==pos) f[0]++;
			f[pos+1]=min(f[pos+1],num[i]);
		}else {
			f[1]=min(f[1],num[i]);
		}
	}
	cout<<f[0];
	fclose(stdin);
	return 0;
}

对于第二问,可使用dilworth定理,翻译成通俗易懂的语言就是:
对于一个序列,其最少的不降子序列划分=最长上升子序列长度,所以对于第二问输出最长上升子序列长度即可。

posted @ 2017-11-20 21:34  Mr_Wolfram  阅读(236)  评论(0编辑  收藏  举报