【模板】最长上升子序列(LIS)及其优化 & 洛谷 AT2827 LIS
最长上升子序列
传送门
题意
对于给定的一个n个数的序列,找到它的一个最长的子序列,并且保证这个子序列是由低到高排序的。
例如,1 6 2 5 4 6 8的最长上升子序列为1 2 4 6 8。
基本思路
非常显然,这类题用dp求解。 dp[i]表示已i为结尾的最长上升子序列的长度,首先枚举每一个末尾i,然后枚举从1到i-1,如果a[1...i-1]<a[i]说明满足上升子序列,就加上一后取max。
附上代码。
1 //LIS O(n^2) 2 #include<iostream> 3 using namespace std; 4 int dp[100005],a[100005],n,maxx=-999; 5 int main(){ 6 cin>>n; 7 for(int i=1;i<=n;i++) cin>>a[i],dp[i]=1; 8 for(int i=1;i<=n;i++){ 9 for(int j=1;j<i;j++){ 10 if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);//如果后面的大于前面的,就取较大的值。 11 } 12 maxx=max(maxx,dp[i]); //每次更新最大值 13 } 14 cout<<maxx; 15 return 0; 16 }
优化
//咕了八个半月的优化。。。。
方法是把dp表示的意义倒过来:dp[i]表示长度为i的最长上升子序列的结尾元素最小是什么。
很显然,dp数组一定是单调递增的。
这时,对于每一个a[i],我们做出判断:
- 当a[i]比当前最长的序列的最后一个数字还大,那么长度++;
- 否则就二分查找dp数组中第一个>=a[i]的数值,并把它改为a[i]。
为什么呢?
因为这第一个大于等于a[i]的数的前一个数是最后一个小于a[i]的数,然后a[i]这个数就可以接到那个数的后面,又因为我们要保存的是最小值,就更新为a[i]。
1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 const int maxn=100005; 5 int n,dp[maxn],a[maxn],len; 6 int main() 7 { 8 cin>>n; 9 for(int i=1;i<=n;i++) cin>>a[i]; 10 for(int i=1;i<=n;i++){ 11 if(a[i]>dp[len]) dp[++len]=a[i]; 12 else{ 13 dp[lower_bound(dp+1,dp+len+1,a[i])-dp]=a[i]; 14 } 15 } 16 cout<<len; 17 return 0; 18 }