1. 程式人生 > >【dp優化】LIS(最長上升子序列)長度的nlogn演算法

【dp優化】LIS(最長上升子序列)長度的nlogn演算法

這道題第一反應就想到了 [CEOI96]渡輪問題 就是一個非常裸的求最長上升子序列的長度,還不要方案,非常的水。然而,常規的dp複雜度是 O(n^2) ,這道題會愉快地TLE,所以要進行nlogn級別的優化。

//O(n^2) TLE
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define LL long long
#define INF 0x3f3f3f3f
#define MAXN 40005
int n;
int a[MAXN],dp[MAXN];
int
main() { int T; scanf("%d",&T); while(T--) { int ans=-1; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); dp[i]=1; for(int j=1;j<i;j++) if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1
); ans=max(ans,dp[i]); } printf("%d\n",ans); } return 0; }

nlogn演算法

其實說實話我覺得這個演算法比常規的動歸思想上更暴力,就是貪心地取,然而複雜度更小。

具體就是:

  • 定義d[k]:長度為k的上升子序列的最末元素,若有多個長度為k的上升子序列,則記錄最小的那個最末元素。
  • 列舉a[i] 對每個a[i]:若a[i]>d[len],那麼len++,d[len] = a[i];
  • 否則,從d[1]到d[len-1]中找到一個j,滿足d[j-1] < a[i]< d[j],則根據d的定義,我們需要更新長度為j的上升子序列的最末元素,即 d[j] = a[i];

    (這裡實際上就是運用了貪心的思路,d[j-1]< a[i]< d[j]的條件保證了正確性,而對於d中的每一個元素,都盡力做到最小,這樣就儘可能地使a[i]>d[len]成立)

//nlogn LIS
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define LL long long
#define INF 0x3f3f3f3f
#define MAXN 40005
int n;
int a[MAXN];
int d[MAXN];//長度為k的上升子序列的最末元素,若有多個,記錄最小
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        int len=1;
        scanf("%d",&a[1]);
        d[1]=a[1];
        for(int i=2;i<=n;i++)
        {
            scanf("%d",&a[i]);
            if(a[i]>d[len])
                len++,d[len]=a[i];
            else 
            {
                int pos=lower_bound(d+1,d+len+1,a[i])-d;
                d[pos]=a[i];
            }
        }
        printf("%d\n",len);
    }
    return 0;
}