例题:导弹拦截
这题目太经典了,我就不放了。
这里就是介绍一下如何求o(nlogn)级别的最长上升子序列长度。本来傻乎乎dp的话,应该是o(n^2)级别的。(思路来自牛客大佬)
贪心策略:如果两个子序列中一个的最后的一个元素的值越小
那么我们越有可能在后面的遍历中给它加上其他元素
用一个数组low[i]表示:长度为i的子序列的末端最小值。
遍历每一个a[i]时找到一个j,如果low[j]<a[i]
那么a[i]可以放在长度为j的子序列的后面,所以low[i+1]可能更新
我们看low[i+1]是否比a[i]大如果low[i+1]>a[i]那么low[i+1]=a[i](显然)
同时注意到,对于low的所有元素,low[j+1]>=low[j]恒成立
就是说长度为j+1的子序列的最后一个元素值一定大于j的子序列的最后一个元素值
这是因为长度为j+1的子序列就包括长度为j的子序列
那么什么时候low[j+1]会更新:当a[i]>a[j]且a[i]<a[j+1]时
结合low是个从小到大的有序序列,我们可以轻易地发现这是二分查找!!
即C++里的lower_bound(序列中的第一个大于等于target的位置)
如果求的不是最长上升子序列而是最长不下降子序列的话就用upper_bound了
因为此时的条件是a[i]>=a[j]且a[i]<a[j]了
如果是下降子序列,只需将原数组到换过来就好了
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n;
ll mas[100010];
ll low[100010];
ll ans1,ans2;
const int inf=30010;
int main()
{
n=1;
while(cin>>mas[n]) n++;
n--;
memset(low,0x3f,sizeof low);
//fill(low+1, low+n+1, inf);
for(int i=1;i<=n;++i){
low[lower_bound(low+1,low+1+n,mas[i])-low]=mas[i];
}
//寻找j使得low【j】<=low【i】&&low【j+1】>low【i】
for(int i=n;i;--i){
if(low[i]!=0x3f3f3f3f3f3f3f3f){
ans2=i;
break;
}
}
//reverse(mas.begin(),mas.end());
reverse(mas+1,mas+1+n);
//memset(low,0x3f,sizeof low);
fill(low+1, low+n+1, inf);//重新初始化不要忘了!!!
for(int i=1;i<=n;++i){
low[upper_bound(low+1,low+1+n,mas[i])-low]=mas[i];
}
for(int i=1;i<=n;++i){
if(low[i]!=inf) ans1=i;
}
//题目,那就完蛋了。
cout<<ans1<<endl<<ans2;
return 0;
}
两次求ans的过程,我用了不同的初始化方法,是因为自己第一次没有注意到,自己开的是long long,所以memset初始化0x3f时,是会变成0x3f3f3f3f3f3f3f3f(不用数了,八个) ,但我一开始只
开了0x3f3f3f3f.....当然,如果用fill的话,就没什么问题,只要保证low的初始化结果是:每一个元素都大于mas的值就可以了,这样就可以保证它能不断更新。找到最后一个被更新的值,它就是最长上升子序列的长度了。