[题目链接](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;
}