• 【LIC】O(nlogn)解法


    【LIC--最长递增子序列问题】 在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增子序列。

    O(nlogn)算法:所需要的数组

    1、数组T

    2、增设一个minT[]数组,minT[x]存放长度为x的最长上升子序列的最小末尾数。

    3、dp[i],从一到元素T[i]结尾的最长上升子序列的长度;

    具体原理转自网络:


    设 T[t]表示序列中的第t个数,dp[t]表示从1到t这一段中以t结尾的最长上升子序列的长度,初始时设dp[t] = 0(t = 1, 2, ..., len(T))。则有动态规划方程:

          dp[t] = max{1, F[j] + 1} (j = 1, 2, ..., t - 1, 且A[j] < A[t])。

    现在,我们仔细考虑计算F[t]时的情况。假设有两个元素A[x]和A[y],满足
    (1)x < y < t
    (2)A[x] < A[y] < A[t]
    (3)dp[x] = dp[y]

    此时,选择dp[x]和选择dp[y]都可以得到同样的dp[t]值,那么,在最长上升子序列的这个位置中,应该选择T[x]还是应该选择T[y]呢?

    很明显,选择T[x]比选择T[y]要好。因为由于条件(2),在T[x+1] ... T[t-1]这一段中,如果存在T[z],T[x] < T[z] < T[y],则与选择T[y]相比,将会得到更长的上升子序列。
    再根据条件(3),我们会得到一个启示:根据dp[]的值进行分类。对于dp[]的每一个取值k,我们只需要保留满足dp[t] = k的所有T[t]中的最小值。设minT[k]记录这个值,即minT[k] = min{A[t]} (F[t] = k)。

    注意到minT[]的两个特点:
    (1) minT[k]的值是在整个计算过程中是单调不下降的。
    (2) minT[]的值是有序的,即minT[1] < minT[2] < minT[3] < ... < minT[n]。

    利用D[],我们可以得到另外一种计算最长上升子序列长度的方法。设当前已经求出的最长上升子序列长度为len。先判断T[t]与minT[len]。若T [t] > minT[len],则将T[t]接在minT[len]后将得到一个更长的上升子序列,len = len + 1, minT[len] = T[t];否则,在minT[1]..minT[len]中,找到最大的j,满足minT[j] < T[t]。令k = j + 1,则有T[t] <= minT[k],将T[t]接在minT[j]后将得到一个更长的上升子序列,更新minT[k] = T[t]。最后,len即为所要求的最长上升子序列的长度。

    由于D[]的特点(2),我们在minT[]中查找时,可以使用二分查找(或lower_bound)高效地完成,则整个算法的时间复杂度下降为O(nlogn),有了非常显著的提高。需要注意的是,minT[]在算法结束后记录的并不是一个符合题意的最长上升子序列!

    以UVa - 10635 - Prince and Princess为例

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstdlib>
     4 #include<cstring>
     5 #include<algorithm>
     6 using namespace std;
     7 const int maxn = 255*255;
     8 const int INF = 0x7fffffff;
     9 int main()
    10 {
    11     int T;
    12     scanf("%d", &T);
    13     for(int kase = 1; kase <= T; kase++)
    14     {
    15         int n, p, q, x, T[maxn], New[maxn], dp[maxn], minT[maxn], ans = 0;
    16         memset(New, 0, sizeof(New));
    17         scanf("%d%d%d", &n, &p, &q);
    18         for(int i = 0; i <= p; i++)
    19         {
    20             scanf("%d", &x);
    21             New[x] = i+1;
    22         }
    23         int k = 0;
    24         for(int i = 0; i <= q; i++)
    25         {
    26             scanf("%d", &x);
    27             if(New[x])
    28                 T[k++] = New[x];
    29         }
    30         for(int i = 1; i <= k; i++) minT[i] = INF;
    31         for(int i = 0; i < k; i++)
    32         {
    33             int m = lower_bound(minT+1, minT+1+k, T[i])-minT; //找到序列中不大于T[i]的最大的数,正因为(二分)查找才使得复杂度变身为n*log(n)
    34             dp[i] = m; //以元素T[i]结尾的最长上升子序列的长度
    35             minT[m] = T[i]; //长度为m的上升子序列中最大元素的值;
    36             ans = max(ans, dp[i]);
    37         }
    38         printf("Case %d: %d
    ", kase, ans);
    39     }
    40     return 0;
    41 }
  • 相关阅读:
    操作系统的用户态和内核态
    C++程序编译过程
    大爽Python入门练习题 15 最长字符串
    大爽Python入门练习题 25 二维列表行列与序数关系
    大爽Python入门练习题 16 三个数找中间值
    大爽Python入门练习题 17 最大差值
    大爽Python入门练习题 19 猜结果
    大爽Python入门练习题 11 倒序生成列表
    大爽Python入门练习题 18 字母次数统计
    大爽Python入门练习题 110 猜函数
  • 原文地址:https://www.cnblogs.com/LLGemini/p/4314663.html
Copyright © 2020-2023  润新知