导弹拦截
这道题可以用树状数组、线段树什么的优化
但是我一个都不会
而这道题的数据范围又很大
c++奥赛一本通(meng)上o(n2)的算法过不了
所以本蒟蒻来说一个o(nlogn)的算法
问题转换
第一问很容易就可以想到是求一个最长不上升子序列
但是第二问就需要一个转换的思想了
引理:Dilworth定理:偏序集的最少反链划分数等于最长链的长度
我们可以把问题中需要几组导弹转化成求一个最长上升子序列
证明: 1、首先我们把这些导弹分为s组(s即为所求答案)
可以看出每一组都是一个不升子序列
2、划分完后我们在组一里找一个原序列里以组一的开头点连续的不升子串的最后一个元素,可以知道在组2中一定有一个大与它的点
(如果组二中没有的话,那么组二中最高的导弹高度必然小于这个点,而其他的高度都小于这个高度而且是递减或相等的,那么没有必要再开一个组二了,矛盾,所以不存在找不到比他大的点的情况)
3、以此类推,对于每一个k组(1<=k<n)都可以找到这样的一些点
所以把这些点连起来,就是一条上升子序列。
4、设最长上升子序列长度为l
所求上升子序列为h
那么h<=l
因为最长上升子序列任意两个不在一组内
(如果在同一个组内,则每个组的数不成为一个不生子序列,矛盾)
所以l==h
比较难理解
我们来看组数据
389 207 155 300 299 170 158 65
组一 389 207 155 65 组二 300 299 170 158
步骤一中我们一开始找到的点是1
因为如果找65不好解释,所以我们找原数列里连续的最后一个即155
组二里可以找到300比他大
所以最长上升子序列长度为2==答案
问题求解
到这里我们发现只要求最长不升子序列和最长上升子序列就好了
下面说最长上升子序列的o(nlogn)求法(最长不升子序列同理)
数组a为要求的数列
首先我们开一个数组k
k[lis]记录lis长的上升子序列的最后一个数
len表示最长的长度
初始化len=0,k[0]=-无限
我们可以很轻松的看出k是一个有序的(递增)
如果a[i](1<=i<=n)>k[len]
k[len=1]=i;
不然每次在k里面二分查找第一个大于等于它的数,下标为x
比较大小,k[x]=min(k[x],a[i])
因为我们要求最长的,所以我们要尽可能的让最后一位小(贪心思想)
最后输出len即可
证明
k数组一定是一个有序的上升序列
因为它记录的是长为x的上升序列的最后一位
如果不是上升的,那么它必然可以加入以k[x+1]结尾的上升序列,矛盾
复杂度
每次最坏要二分查找(log len) 一共要n次 len<=n 所以算法复杂度为o(nlogn)
过这个题稳得一批
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=100005;
int a[MAXN],n,d1[MAXN],d2[MAXN],len1=1,len2=1;
bool cmp(const int &a,const int &b) {
return a>b;}
int main() {
while(scanf("%d",&a[++n])!=EOF) {
}
n--;
d1[1]=a[1],d2[1]=a[1];
//d1从大到小,d2从小到大
for(int i=2;i<=n;i++) {
//求最长不上升子序列
if(d1[len1]>=a[i]) d1[++len1]=a[i];
else {
//找第一个小于a[i]的数(倒序排)
int it=upper_bound(d1+1,d1+1+len1,a[i],cmp)-d1;
d1[it]=a[i];
}
//求最长上升子序列
if(d2[len2]<a[i]) d2[++len2]=a[i];
else {
//找第一个大于等于a[i]的数
int it=lower_bound(d2+1,d2+1+len2,a[i])-d2;
d2[it]=a[i];
}
}
printf("%d\n%d",len1,len2);
return 0;
}