LIS是动态规划中经典的问题。
首先我们令f[i]表示前i个元素中最长上升(不下降同理)子序列长度,那么我们有:
f[i]=min{f[j] + 1 | a[i] > a[j]}
朴素的算法当中我们在外层枚举1n,内层枚举1i-1来挨个比较转移,这样的时间复杂度是 (O(n^{2}))的。
考虑优化。我们可以在求出当前最长上升子序列时试着储存一下此时的子序列到底是什么。但是请注意,这并不代表储存的序列就是最终答案!
那么我们就可以对于此时储存的子序列使用二分,对于当前的a[i],我们只需要在储存的子序列当中找到第一个大于a[i]的将它变成a[i],因为同样的序列长度的情况下各个元素的值越小对于后面的元素加入序列越有利。如果此时储存的序列中最大值都小于a[i],那么就将序列长度加1并且将a[i]放入队尾即可。
下面感性证明此做法的正确性。前面就已经说过这样储存的子序列并不是最终的最长上升子序列,但是序列长度却是最长上升子序列的长度,这是因为扩张子序列长度操作是在当且仅当a[i]大于此时队尾元素时才发生。而我们使用二分将队列中间的元素替换只是为了使后面的待枚举的元素更有希望入队,这里其实涉及到贪心的思想,前面也解释过,同样的长度当然是元素的值越小越好,因为元素的值越小后面待枚举的元素就更有可能入队,最终的答案一定不小于此时同样长度却各个元素更大的序列答案。
#include<bits/stdc++.h>using namespace std;int n;
int a[100010];
int d[100010];
int len;int main() {cin >> n;for(int i = 1; i <= n;i++) {cin >> a[i];}d[++len] = a[1];for(int i = 2;i <= n;i++) {if(a[i] > d[len]) d[++len] = a[i];else d[upper_bound(d+1,d+1+len,a[i])-d] = a[i];}cout << len;return 0;
}