题目:
注意:数据范围高达1e5,所以用O(n^2)的时间复杂度肯定不行,也就是普通的两重循环的dp不行了……所以从这题学到(只是看懂)了dp+二分的解法,还有用线段树求最长上升子序列长度的,后面这两种都是O(nlogn)的复杂度,(雪长说线段树的“更容易理解”:“相当于把dp 的第二层循环变为log的”)【但问题是线段树我忘光了呀qwq】
转化:此题第一问即(我)常见的 求最长不上升子序列长度(只是数据限制的方法我个菜鸡不常见……)重点是第二问,由Dilworth定理得:可划分的最少不上升子序列的数目就是其最长下降子序列的长度
然后自己对着样例走一遍就好,上图可谓难得的讲得“通俗易懂”的惹QAQ
注意lower_bound(s,s+n,a[i])是在s的[ 0,)中找到第一个“大于等于”a[i]的位置,那么对于s此时为50(s[0]),170(s[1]),而在检索a数组时又碰到一个170,那么(lower_bound)所找到的d==1,s[1]再次被赋予了170那么最后得到的s就没有重复的元素,所以它是“上升”的,不是“不下降”的
那么同样的道理,upper_bound找的是第一个“大于”的位置,就不是覆盖掉相等的元素,也即可重复存在,是最长“不下降”子序列用的。【当然你把lower_bound里的a[i]加个1与upper_bound a[i]作用相同哈】
#include<iostream>
#include<algorithm>
using namespace std;
const int M=1e5+6,inf=0x3f3f3f3f;
int n,a[M],ct,len,s[M];//Dilworth定理
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
while(cin>>a[n]){n++;}
fill(s,s+n,inf);
for(int i=0;i<n;i++){ //求最长"上升"子序列长度
int d=lower_bound(s,s+n,a[i])-s; //找第一个大于等于该数的位置
s[d]=a[i];
ct=max(ct,d);
}
fill(s,s+n,inf);
reverse(a,a+n); //倒过来 再求最长"不下降"子序列长度
for(int i=0;i<n;i++){
int d=upper_bound(s,s+n,a[i])-s;
s[d]=a[i];
len=max(len,d);
}
cout<<len+1<<'\n'<<ct+1;//因下标从0开始,故长度为下标+1
return 0;
}
线段树不会,先就这样了叭这题(润