最長上升子序列 (LIS) 詳解+例題模板 (全)(轉)


1.摘要:
       關於LIS部分,本篇博客講一下LIS的概念定義和理解,以及求LIS的三種方法,分別是O(n^2)的DP,O(nlogn)的二分+貪心法,以及O(nlogn)的樹狀數組優化的DP,最后附上幾道非常經典的LIS的例題及分析。

 

2.LIS的定義:
        最長上升子序列(Longest  Increasing Subsequence),簡稱LIS,也有些情況求的是最長非降序子序列,二者區別就是序列中是否可以有相等的數。假設我們有一個序列 b i,當b1 < b2 < … < bS的時候,我們稱這個序列是上升的。對於給定的一個序列(a1, a2, …, aN),我們也可以從中得到一些上升的子序列(ai1, ai2, …, aiK),這里1 <= i1 < i2 < … < iK <= N,但必須按照從前到后的順序。比如,對於序列(1, 7, 3, 5, 9, 4, 8),我們就會得到一些上升的子序列,如(1, 7, 9), (3, 4, 8), (1, 3, 5, 8)等等,而這些子序列中最長的(如子序列(1, 3, 5, 8) ),它的長度為4,因此該序列的最長上升子序列長度為4。

        是不是覺得好抽象~沒事我給你解釋~

        首先需要知道,子串和子序列的概念,我們以字符子串和字符子序列為例,更為形象,也能順帶着理解字符的子串和子序列:

     (1)字符子串指的是字符串中連續的n個字符,如abcdefg中,ab,cde,fg等都屬於它的字串。

     (2)字符子序列指的是字符串中不一定連續但先后順序一致的n個字符,即可以去掉字符串中的部分字符,但不可改變其前后順序。如abcdefg中,acdg,bdf屬於它的子序列,而bac,dbfg則不是,因為它們與字符串的字符順序不一致。

       知道了這個,數值的子序列就很好明白了,即用數組成的子序列。這樣的話,最長上升子序列也很容易明白了,歸根結底還是子序列,然后子序列中,按照上升順序排列的最長的就是我們最長上升子序列了,這樣聽來是不是就很容易明白啦~

       還有一個非常重要的問題:請大家用集合的觀點來理解這些概念,子序列、公共子序列以及最長公共子序列都不唯一,但很顯然,對於固定的數組,雖然LIS序列不一定唯一,但LIS的長度是唯一的。再拿我們剛剛舉的栗子來講,給出序列 ( 1, 7, 3, 5, 9, 4, 8),易得最長上升子序列長度為4,這是確定的,但序列可以為 ( 1, 3, 5, 8 ), 也可以為 ( 1, 3, 5, 9 )。

 

3.LIS長度的求解方法:
那么這個到底該怎么求呢?

這里詳細介紹一下求LIS的三種方法,分別是O(n^2)的DP,O(nlogn)的二分+貪心法,以及O(nlogn)的樹狀數組優化的DP。

 

解法1:動態規划:
  我們都知道,動態規划的一個特點就是當前解可以由上一個階段的解推出, 由此,把我們要求的問題簡化成一個更小的子問題。子問題具有相同的求解方式,只不過是規模小了而已。最長上升子序列就符合這一特性。我們要求n個數的最長上升子序列,可以求前n-1個數的最長上升子序列,再跟第n個數進行判斷。求前n-1個數的最長上升子序列,可以通過求前n-2個數的最長上升子序列……直到求前1個數的最長上升子序列,此時LIS當然為1。

  讓我們舉個例子:求 2 7 1 5 6 4 3 8 9 的最長上升子序列。我們定義d(i) (i∈[1,n])來表示前i個數以A[i]結尾的最長上升子序列長度。

  前1個數 d(1)=1 子序列為2;

  前2個數 7前面有2小於7 d(2)=d(1)+1=2 子序列為2 7

  前3個數 在1前面沒有比1更小的,1自身組成長度為1的子序列 d(3)=1 子序列為1

  前4個數 5前面有2小於5 d(4)=d(1)+1=2 子序列為2 5

  前5個數 6前面有2 5小於6 d(5)=d(4)+1=3 子序列為2 5 6

  前6個數 4前面有2小於4 d(6)=d(1)+1=2 子序列為2 4

  前7個數 3前面有2小於3 d(3)=d(1)+1=2 子序列為2 3

  前8個數 8前面有2 5 6小於8 d(8)=d(5)+1=4 子序列為2 5 6 8

  前9個數 9前面有2 5 6 8小於9 d(9)=d(8)+1=5 子序列為2 5 6 8 9

  d(i)=max{d(1),d(2),……,d(i)} 我們可以看出這9個數的LIS為d(9)=5

  總結一下,d(i)就是找以A[i]結尾的,在A[i]之前的最長上升子序列+1,當A[i]之前沒有比A[i]更小的數時,d(i)=1。所有的d(i)里面最大的那個就是最長上升子序列。其實說的通俗點,就是每次都向前找比它小的數和比它大的數的位置,將第一個比它大的替換掉,這樣操作雖然LIS序列的具體數字可能會變,但是很明顯LIS長度還是不變的,因為只是把數替換掉了,並沒有改變增加或者減少長度。但是我們通過這種方式是無法求出最長上升子序列具體是什么的,這點和最長公共子序列不同。

如果非要求LIS具體序列,闊以參考一下 ZOJ 4028, 即第15屆浙江省賽題E題 ------ LIS,題解戳這里~ 。

 

 

 

 

 

 

 

狀態設計:F [ i ] 代表以 A [ i ] 結尾的 LIS 的長度

狀態轉移:F [ i ] = max { F [ j ] + 1 ,F [ i ] } (1 <= j <  i,A[ j ] < A[ i ])

邊界處理:F [ i ] = 1 (1 <= i <= n)

時間復雜度:O (n^2)

話不多說,show me the code!

代碼實現:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 103, INF = 0x7f7f7f7f;
int a[maxn], f[maxn];
int n,ans = -INF;
int main()
{
    scanf("%d", &n);
    for(int i=1; i<=n; i++) 
    {
        scanf("%d", &a[i]);
        f[i] = 1;
    }
    for(int i=1; i<=n; i++)
        for(int j=1; j<i; j++)
            if(a[j] < a[i])
                f[i] = max(f[i], f[j]+1);
    for(int i=1; i<=n; i++) 
        ans = max(ans, f[i]);
    printf("%d\n", ans);
    return 0;
}

這個算法的時間復雜度為〇(n²),並不是最優的算法。在限制條件苛刻的情況下,這種方法行不通。那么怎么辦呢!有沒有時間復雜度更小的算法呢?說到這里了,當然是有的啦!還有一種時間復雜度為〇(nlogn)的算法,下面就來看看。

 

解法2:貪心+二分:
思路:

新建一個 low 數組,low [ i ]表示長度為i的LIS結尾元素的最小值。對於一個上升子序列,顯然其結尾元素越小,越有利於在后面接其他的元素,也就越可能變得更長。因此,我們只需要維護 low 數組,對於每一個a[ i ],如果a[ i ] > low [當前最長的LIS長度],就把 a [ i ]接到當前最長的LIS后面,即low [++當前最長的LIS長度] = a [ i ]。 
那么,怎么維護 low 數組呢?
對於每一個a [ i ],如果a [ i ]能接到 LIS 后面,就接上去;否則,就用 a [ i ] 取更新 low 數組。具體方法是,在low數組中找到第一個大於等於a [ i ]的元素low [ j ],用a [ i ]去更新 low [ j ]。如果從頭到尾掃一遍 low 數組的話,時間復雜度仍是O(n^2)。我們注意到 low 數組內部一定是單調不降的,所有我們可以二分 low 數組,找出第一個大於等於a[ i ]的元素。二分一次 low 數組的時間復雜度的O(lgn),所以總的時間復雜度是O(nlogn)。

  我們再舉一個例子:有以下序列A[ ] = 3 1 2 6 4 5 10 7,求LIS長度。

  我們定義一個B[ i ]來儲存可能的排序序列,len 為LIS長度。我們依次把A[ i ]有序地放進B[ i ]里。

     (為了方便,i的范圍就從1~n表示第i個數)

  A[1] = 3,把3放進B[1],此時B[1] = 3,此時len = 1,最小末尾是3

  A[2] = 1,因為1比3小,所以可以把B[1]中的3替換為1,此時B[1] = 1,此時len = 1,最小末尾是1

  A[3] = 2,2大於1,就把2放進B[2] = 2,此時B[ ]={1,2},len = 2

  同理,A[4]=6,把6放進B[3] = 6,B[ ]={1,2,6},len = 3

  A[5]=4,4在2和6之間,比6小,可以把B[3]替換為4,B[ ] = {1,2,4},len = 3

  A[6] = 5,B[4] = 5,B[ ] = {1,2,4,5},len = 4 

  A[7] = 10,B[5] = 10,B[ ] = {1,2,4,5,10},len = 5

  A[8] = 7,7在5和10之間,比10小,可以把B[5]替換為7,B[ ] = {1,2,4,5,7},len = 5

       最終我們得出LIS長度為5,但是,但是!!!B[ ] 中的序列並不一定是正確的最長上升子序列。在這個例子中,我們得到的1 2 4 5 7 恰好是正確的最長上升子序列,下面我們再舉一個例子:有以下序列A[ ] = 1 4 7 2 5 9 10 3,求LIS長度。

       A[1] = 1,把1放進B[1],此時B[1] = 1,B[ ] = {1},len = 1

       A[2] = 4,把4放進B[2],此時B[2] = 4,B[ ] = {1,4},len = 2

       A[3] = 7,把7放進B[3],此時B[3] = 7,B[ ] = {1,4,7},len = 3

       A[4] = 2,因為2比4小,所以把B[2]中的4替換為2,此時B[ ] = {1,2,7},len = 3

       A[5] = 5,因為5比7小,所以把B[3]中的7替換為5,此時B[ ] = {1,2,5},len = 3

       A[6] = 9,把9放進B[4],此時B[4] = 9,B[ ] = {1,2,5,9},len = 4

       A[7] = 10,把10放進B[5],此時B[5] = 10,B[ ] = {1,2,5,9,10},len = 5

       A[8] = 3,因為3比5小,所以把B[3]中的5替換為3,此時B[ ] = {1,2,3,9,10},len = 5

  最終我們得出LIS長度為5。但是,但是!!這里的1 2 3 9 10很明顯並不是正確的最長上升子序列。因此,B序列並不一定表示最長上升子序列,它只表示相應最長子序列長度的排好序的最小序列。這有什么用呢?我們最后一步3替換5並沒有增加最長子序列的長度,而這一步的意義,在於記錄最小序列,代表了一種“最可能性”,只是此種算法為計算LIS而進行的一種替換。假如后面還有兩個數據12和15,那么B[ ]將繼續更新。

  因為在B中插入的數據是有序的,不需要移動,只需要替換,所以可以用二分查找插入的位置,那么插入n個數的時間復雜度為〇(logn),這樣我們會把這個求LIS長度的算法復雜度降為了〇(nlogn)。話不多說了,show me the code!

代碼實現:

#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
 
const int maxn =300003, INF = 0x7f7f7f7f;
int low[maxn], a[maxn];
int n, ans;
 
int binary_search(int *a, int R, int x)
//二分查找,返回a數組中第一個>=x的位置 
{
    int L = 1, mid;
    while(L <= R)
    {
        mid = (L+R) >> 1;
        if(a[mid] <= x)
            L = mid + 1;
        else 
            R = mid - 1;
    }
    return L;
}
 
int main()
{
    scanf("%d", &n);
    for(int i=1; i<=n; i++) 
    {
        scanf("%d", &a[i]); 
        low[i] = INF;   //由於low中存的是最小值,所以low初始化為INF 
    }
    low[1] = a[1]; 
    ans = 1;   //初始時LIS長度為1 
    for(int i=2; i<=n; i++)
    {
        if(a[i] > low[ans])    //若a[i]>=low[ans],直接把a[i]接到后面 
            low[++ans] = a[i];
        else       //否則,找到low中第一個>=a[i]的位置low[j],用a[i]更新low[j] 
            low[binary_search(low, ans, a[i])] = a[i];
    }
    printf("%d\n", ans);   //輸出答案 
    return 0;
}

這其中用到了二分查找第一個大於等於的,其實C++里面的有一個函數可用代替二分,那就是 —— low_bound( )函數。

lower_bound( )函數:
下面是使用lower_bound優化最長上升子序列。由於長度相同的上升子序列只需要保存結尾最小的那個,而長度遞增時,結尾數字的大小也是遞增的。最長上升子序列就是找出比他大的第一個數。前面的數都比他小,所以他和這個數的長度相同。然后由於他比較然后小,更新找到的那個值。

#include<stdio.h>  
#include<string.h>  
#include<algorithm>  
  
using namespace std;  
  
int num[10]={3,6,3,2,4,6,7,5,4,3};  
  
const int INF=0x3f3f3f3f;  
int l=10, g[100], d[100];  
 
int main()  
{  
    fill(g, g+l, INF);  
    int max_=-1;  
    for(int i=0; i<l; i++)  
    {  
        int j = lower_bound(g, g+l, num[i]) - g;  
        d[i] = j+1;  
        if(max_<d[i])  
            max_=d[i];  
        g[j] = num[i];  
    }  
    printf("%d\n", max_);  
    return 0;  
}  
  
這個算法其實已經不是DP了,有點像貪心。至於復雜度降低其實是因為這個算法里面用到了二分搜索。
本來有N個數要處理是O(n),每次計算要查找N次還是O(n),一共就是O(n^2);
現在搜索換成了O(logn)的二分搜索,總的復雜度就變為O(nlogn)了。
這里主要注意一下lower_bound函數的應用,注意減去的g是地址。
地址 - 地址 = 下標。

解法3:樹狀數組維護:
我們再來回顧O(n^2)DP的狀態轉移方程:F [ i ] = max { F [ j ] + 1 ,F [ i ] }  (1 <= j <  i,A[ j ] < A[ i ])
我們在遞推F數組的時候,每次都要把F數組掃一遍求F[ j ]的最大值,時間開銷比較大。我們可以借助數據結構來優化這個過程。用樹狀數組來維護F數組(據說分塊也是可以的,但是分塊是O(n*sqrt(n))的時間復雜度,不如樹狀數組跑得快),首先把A數組從小到大排序,同時把A[ i ]在排序之前的序號記錄下來。然后從小到大枚舉A[ i ],每次用編號小於等於A[ i ]編號的元素的LIS長度+1來更新答案,同時把編號大於等於A[ i ]編號元素的LIS長度+1。因為A數組已經是有序的,所以可以直接更新。有點繞,具體看代碼。

還有一點需要注意:樹狀數組求LIS不去重的話就變成了最長不下降子序列了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn =103,INF=0x7f7f7f7f;
struct Node{
    int val,num;
}z[maxn]; 
int T[maxn];
int n;
bool cmp(Node a,Node b)
{
    return a.val==b.val?a.num<b.num:a.val<b.val;
}
void modify(int x,int y)//把val[x]替換為val[x]和y中較大的數 
{
    for(;x<=n;x+=x&(-x)) T[x]=max(T[x],y);
}
int query(int x)//返回val[1]~val[x]中的最大值 
{
    int res=-INF;
    for(;x;x-=x&(-x)) res=max(res,T[x]);
    return res;
}
int main()
{
    int ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&z[i].val);
        z[i].num=i;//記住val[i]的編號,有點類似於離散化的處理,但沒有去重 
    }
    sort(z+1,z+n+1,cmp);//以權值為第一關鍵字從小到大排序 
    for(int i=1;i<=n;i++)//按權值從小到大枚舉 
    {
        int maxx=query(z[i].num);//查詢編號小於等於num[i]的LIS最大長度
        modify(z[i].num,++maxx);//把長度+1,再去更新前面的LIS長度
        ans=max(ans,maxx);//更新答案
    }
    printf("%d\n",ans);
    return 0;
}

4.經典例題模板:
 

例1:Super Jumping! Jumping! Jumping! 
Description
Nowadays, a kind of chess game called “Super Jumping! Jumping! Jumping!” is very popular in HDU. Maybe you are a good boy, and know little about this game, so I introduce it to you now. 

The game can be played by two or more than two players. It consists of a chessboard(棋盤)and some chessmen(棋子), and all chessmen are marked by a positive integer or “start” or “end”. The player starts from start-point and must jumps into end-point finally. In the course of jumping, the player will visit the chessmen in the path, but everyone must jumps from one chessman to another absolutely bigger (you can assume start-point is a minimum and end-point is a maximum.). And all players cannot go backwards. One jumping can go from a chessman to next, also can go across many chessmen, and even you can straightly get to end-point from start-point. Of course you get zero point in this situation. A player is a winner if and only if he can get a bigger score according to his jumping solution. Note that your score comes from the sum of value on the chessmen in you jumping path. 
Your task is to output the maximum value according to the given chessmen list. 

Input
Input contains multiple test cases. Each test case is described in a line as follow: 
N value_1 value_2 …value_N 
It is guarantied that N is not more than 1000 and all value_i are in the range of 32-int. 
A test case starting with 0 terminates the input and this test case is not to be processed. 

Output
For each case, print the maximum according to rules, and one line one case. 

Sample Input
3 1 3 2
4 1 2 3 4
4 3 3 2 1
0

//每行一個整數n表示改行輸入的個數,然后是輸入的序列。求最大遞增子序列
Sample Output
4
10
3
思路:

題意是有N個數字構成的序列,求最大遞增子段和,即遞增子序列和的最大值,思路就是定義dp[i],表示以a[i]結尾的最大遞增子段和,雙重for循環,每次求出以a[i]結尾的最大遞增子段和。

 

代碼:

#include<math.h>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#include<queue>
#define INF 0x3f3f3f3f
 
using namespace std;
 
int main()
{
    int a[1005],dp[1005],n,i,j,max1;
    while(scanf("%d",&n)&&n){
        max1=0;
        memset(dp,0,sizeof(dp));
        for(i=0;i<=n-1;i++)
            scanf("%d",&a[i]);
        dp[0]=a[0];
        for(i=1;i<=n-1;i++){
            for(j=0;j<=i-1;j++){
                if(a[i]>a[j])
                    dp[i]=max(dp[j]+a[i],dp[i]);
            }
            dp[i]=max(dp[i],a[i]);
        }
        max1=dp[0];
        for(i=0;i<=n-1;i++)
            max1=max(dp[i],max1);
        printf("%d\n",max1);
    }
    return 0;
}

例2:FatMouse's Speed
Description
FatMouse believes that the fatter a mouse is, the faster it runs. To disprove this, you want to take the data on a collection of mice and put as large a subset of this data as possible into a sequence so that the weights are increasing, but the speeds are decreasing. 

Input
Input contains data for a bunch of mice, one mouse per line, terminated by end of file. 

The data for a particular mouse will consist of a pair of integers: the first representing its size in grams and the second representing its speed in centimeters per second. Both integers are between 1 and 10000. The data in each test case will contain information for at most 1000 mice. 

Two mice may have the same weight, the same speed, or even the same weight and speed. 

Output
Your program should output a sequence of lines of data; the first line should contain a number n; the remaining n lines should each contain a single positive integer (each one representing a mouse). If these n integers are m[1], m[2],..., m[n] then it must be the case that W[m[1]] < W[m[2]] < ... < W[m[n]]  and  S[m[1]] > S[m[2]] > ... > S[m[n]] . In order for the answer to be correct, n should be as large as possible. All inequalities are strict: weights must be strictly increasing, and speeds must be strictly decreasing. There may be many correct outputs for a given input, your program only needs to find one. 

Sample Input
6008 1300
6000 2100
500 2000
1000 4000
1100 3000
6000 2000
8000 1400
6000 1200
2000 1900
Sample Output
4
4
5
9
8
思路:

題意是:給你許多組數據,沒組兩個數,一個代表老鼠的重量,一個代表老鼠的速度,為了證明老鼠越重速度越慢,讓你取出幾組數據證明,問最多能取出幾組。體重要嚴格遞增,速度嚴格遞減,有些思維含量。

 思路:首先按照w遞增排序,如果w相等則s遞減。然后對於i=0;i<size;分別j=0;j<i-1;來查找前面的m是否符合w[i]>w[j]&&s[i]<s[j];如果符合,則dp[i]=dp[j]+1,並且記錄pre。最后記錄最大長度的dp[k]的k,然后倒推回去路徑

代碼:

#include<math.h>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#include<queue>
#define INF 0x3f3f3f3f
 
using namespace std;
 
struct A{
    int w;
    int s;
    int xb;
}a[1010];
 
struct U{
    int num;
    int xb;
}dp[1010];
 
bool cmp(struct A a,struct A b){
    if(a.w==b.w)
        return a.s>b.s;
    else return a.w<b.w;
}
 
int main()
{
    int b[1010],i,j,k,n=0,max1=0;
    while(scanf("%d%d",&a[n].w,&a[n].s)!=EOF){
        ++n;
        a[n-1].xb=n;
    }
    sort(a,a+n,cmp);
    for(i=0;i<=n-1;i++){
        dp[i].num=1;
        dp[i].xb=0;
    }
    for(i=1;i<=n-1;i++){
        for(j=0;j<=i-1;j++){
            if(a[i].w>a[j].w&&a[i].s<a[j].s&&dp[i].num<dp[j].num+1){
                dp[i].num=dp[j].num+1;
                dp[i].xb=j;
            }
        }
        if(dp[i].num>max1)
        {
            max1=dp[i].num;
            k=i;
        }
    }
    for(i=1;i<=max1;i++)
    {
        b[i]=k;
        k=dp[k].xb;
    }
    printf("%d\n",max1);
    for(i=max1;i>=1;i--)
        printf("%d\n",a[b[i]].xb);
    return 0;
}

例3:最少攔截系統
Decription
某國為了防御敵國的導彈襲擊,發展出一種導彈攔截系統.但是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈能夠到達任意的高度,但是以后每一發炮彈都不能超過前一發的高度.某天,雷達捕捉到敵國的導彈來襲.由於該系統還在試用階段,所以只有一套系統,因此有可能不能攔截所有的導彈. 
怎么辦呢?多搞幾套系統唄!你說說倒蠻容易,成本呢?成本是個大問題啊.所以俺就到這里來求救了,請幫助計算一下最少需要多少套攔截系統. 

Input
輸入若干組數據.每組數據包括:導彈總個數(正整數),導彈依此飛來的高度(雷達給出的高度數據是不大於30000的正整數,用空格分隔) 

Output
對應每組數據輸出攔截所有導彈最少要配備多少套這種導彈攔截系統. 

Sample Input
8 389 207 155 300 299 170 158 65
Sample Output
2
思路:

這題是一個貪心+LIS,用dp數組存儲已經部署好的防御系統能打的最大高度,每來一個新的導彈,判斷之前已經部署好的防御系統能否打下當前導彈,如果能的話就選那個最垃圾的防御系統來攻擊導彈,如果之前已經部署的最厲害的防御系統也打不下來的話,那么就新部署一個攔截系統來攔截當前導彈。

 

代碼:

#include<cmath>
#include<deque>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#define INS 0x3f3f3f3f
#define eps 1e-10
 
using namespace std;
 
int a[10001],dp[10001];
 
int main()
{
    int n,i,j,k,z,min1;
    while(scanf("%d",&n)!=EOF)
    {
        for(i=0; i<=n-1; i++)
            scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        k=1;
        dp[0]=a[0];
        for(i=1; i<=n-1; i++)
        {
            z=-1;
            min1=0x3f3f3f3f;
            for(j=0; j<=k-1; j++)
                if(dp[j]<min1&&a[i]<=dp[j])
                {
                    z=j;
                    min1=dp[j];
                }
            if(z==-1)
                dp[k++]=a[i];
            else
                dp[z]=a[i];
        }
        printf("%d\n",k);
    }
    return 0;
}

例4:Bridging signals
Description
'Oh no, they've done it again', cries the chief designer at the Waferland chip factory. Once more the routing designers have screwed up completely, making the signals on the chip connecting the ports of two functional blocks cross each other all over the place. At this late stage of the process, it is too 
expensive to redo the routing. Instead, the engineers have to bridge the signals, using the third dimension, so that no two signals cross. However, bridging is a complicated operation, and thus it is desirable to bridge as few signals as possible. The call for a computer program that finds the maximum number of signals which may be connected on the silicon surface without rossing each other, is imminent. Bearing in mind that there may be housands of signal ports at the boundary of a functional block, the problem asks quite a lot of the programmer. Are you up to the task? 

Figure 1. To the left: The two blocks' ports and their signal mapping (4,2,6,3,1,5). To the right: At most three signals may be routed on the silicon surface without crossing each other. The dashed signals must be bridged. 

A typical situation is schematically depicted in figure 1. The ports of the two functional blocks are numbered from 1 to p, from top to bottom. The signal mapping is described by a permutation of the numbers 1 to p in the form of a list of p unique numbers in the range 1 to p, in which the i:th number pecifies which port on the right side should be connected to the i:th port on the left side. 
Two signals cross if and only if the straight lines connecting the two ports of each pair do.

Input
On the first line of the input, there is a single positive integer n, telling the number of test scenarios to follow. Each test scenario begins with a line containing a single positive integer p<40000, the number of ports on the two functional blocks. Then follow p lines, describing the signal mapping: On the i:th line is the port number of the block on the right side which should be connected to the i:th port of the block on the left side.

Output
For each test scenario, output one line containing the maximum number of signals which may be routed on the silicon surface without crossing each other.

Sample Input

4
6
4
2
6
3
1
5
10
2
3
4
5
6
7
8
9
10
1
8
8
7
6
5
4
3
2
1
9
5
8
9
2
3
1
7
4
6


Sample Output

3
9
1
4

 


思路:

本題為模板題,但需注意不能用普通做法,需要用二分優化,否則會T的。

 

代碼:

#include<math.h>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#include<queue>
 
using namespace std;
 
int a[40001],dp[40001];
 
int main()
{
    int T,n,i,len,pos;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        len=1;dp[1]=a[1];
        for(i=2; i<=n; i++)
        {
            if(a[i]<=dp[len])
            {
                pos=lower_bound(dp,dp+len,a[i])-dp;
                dp[pos]=a[i];
            }
            else
            {
                len++;
                dp[len]=a[i];
            }
        }
        printf("%d\n",len);
    }
    return 0;
}

注意:

一般來說,為防止T,都會用二分法來找,最為簡便的就是通過lower_bound( )函數來進行查找,最常用模板請參考例題,尤其例4。.

 

 

5.相關知識:( 建議放在一起比較區分 )
1)最長公共子序列(LCS)戳這里 ~ 

2)最長回文子串 and 最長回文子序列  (LPS)  戳這里 ~
————————————————
版權聲明:本文為CSDN博主「lxt_Lucia」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/lxt_Lucia/article/details/81206439


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM