大家好我来填坑了QwQ
在刚刚接触DP和贪心的时候,教练给我们讲的例题就是导弹拦截,那个时候的标准复杂度是O(n^2)的,后来不知道什么时候洛谷把这个题的数据加强了,给了200分的O(nlogn)复杂度
下面是正文:
首先是对中心思想的阐述:一个序列中的单调子序列的个数等于最长单调序列的长度(叫做Dilworth定理,我也没推出来,但这不妨碍我们用它)
第一个问题
在第一个问题中,lis[len] 存储长度为len的不下降子序列结束点的最大值(也由此可以看出,我们无法得到这个子序列,只能得到长度)
这个最大值运用了贪心的思想,显然我们记录结束点的最大值是对接下来的情况的最好状态。
初始化将len=1,lis[len]=a[1],sil[len]=-a[1]
遍历一边原序列(2~n),依次判断a[i]与lis[i]的大小关系,我们要求的是最长不上升子序列所以len[len]与a[i]的关系为:


1 if(a[i]<=lis[len]) len++,lis[len]=a[i],sil[len]=-a[i]; 2 else{ 3 int t; 4 t=upper_bound(sil+1,sil+len+1,-a[i])-sil; 5 lis[t]=a[i]; 6 sil[t]=-a[i]; 7 }
a[i]小于等于lis[len],说明a[i]比目前最长的不升序列的最大结束点要小,则该序列的长度就可以增加了,目前新的lis[len]存储的便是a[i]
a[i]大于lis[len],说明a[i]并不能是len增加,但是a[i]可以更新某一个长度为t的不升序列的结束点最大值
我们使用upper_bound()来查找这个t ,维护一个sil[t]数组存储lis[t]的相反数,来维护不升序列upper_bound()
关于upper_bound()和lower_bound()的用法,在另一篇随笔里有提到,大家可以去看看 指路二分
第二个问题


1 len=1; 2 lis[1]=a[1]; 3 for(int i=2;i<=n;i++){ 4 if(a[i]>lis[len]) len++,lis[len]=a[i]; 5 else{ 6 int t=lower_bound(lis+1,lis+len+1,a[i])-lis; 7 lis[t]=a[i]; 8 } 9 }
求最长不降子序列,思想和第一个问题一样,几乎没有差别,不过我们这里不需要sil的维护了


1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=100010; 4 int n; 5 int a[maxn]; 6 int lis[maxn]; 7 int sil[maxn]; 8 int len=1; 9 int main(){ 10 n=1; 11 while(cin>>a[n]) n++; 12 n--; 13 lis[1]=a[1]; 14 sil[1]=-a[1]; 15 for(int i=2;i<=n;i++) 16 { 17 if(a[i]<=lis[len]) len++,lis[len]=a[i],sil[len]=-a[i]; 18 else 19 { 20 int t; 21 t=upper_bound(sil+1,sil+len+1,-a[i])-sil; 22 lis[t]=a[i]; 23 sil[t]=-a[i]; 24 } 25 } 26 cout<<len<<endl; 27 memset(lis,0,sizeof(lis)); 28 memset(sil,0,sizeof(sil)); 29 len=1; 30 lis[1]=a[1]; 31 for(int i=2;i<=n;i++) 32 { 33 if(a[i]>lis[len]) len++,lis[len]=a[i]; 34 else 35 { 36 int t=lower_bound(lis+1,lis+len+1,a[i])-lis; 37 lis[t]=a[i]; 38 } 39 } 40 cout<<len<<endl; 41 return 0; 42 }