最長上公升子串行(LIS)(模板)

2021-08-21 17:03:19 字數 3629 閱讀 3538

最長遞增子串行(longest increasing subsequence)下面我們簡記為:lis。

假設存在乙個序列d[1..9] = 2 1 5 3 6 4 8 9 7,我們可以很輕鬆的看出來它的lis長度為5。

但是如果乙個序列太長後,就不能直接看出來了!

下面我們試著逐步找出答案。

我們定義乙個序列b,然後令 i = 1 to 9 逐個考察這個序列。

此外,我們用乙個變數len記錄目前最長算到多少了

首先,把d[1]有序地放到b裡,令b[1] = 2,表示當只有1乙個數字2的時候,長度為1的lis的最小末尾是2。這時的len = 1。

然後,把d[2]有序地放到b裡,令b[1] = 1,表示長度為1的lis的最小末尾是1,d[1]=2已經沒用了,同樣這時的len = 1。

接著,d[3] = 5,d[3] > b[1],所以令b[1+1] = b[2] = d[3] = 5,表示長度為2的lis的最小末尾是5,這時候b[1..2] = 1, 5,這時的len = 2。

接著,d[4] = 3,它正好夾在了1和5之間,放在1的位置顯然不合適,因為1小於3,長度為1的lis最小末尾應該是1,這樣很容易推知,長度為2的lis最小末尾是3,於是把5淘汰掉,這時候b[1..2] = 1, 3,這時len = 2。

繼續,d[5] = 6,它在3後面,因為b[2] = 3, 而6在3後面,於是很容易可以推知b[3] = 6, 這時b[1..3] = 1, 3, 6,這時的len = 3。

第6個, d[6] = 4,你看它在3和6之間,於是我們就可以把6替換掉,得到b[3] = 4。b[1..3] = 1, 3, 4,同樣這時len = 3。

第7個, d[7] = 8,它很大,比4大,於是b[4] = 8,這時的len = 4。

第8個, d[8] = 9,得到b[5] = 9,這時的len = 5。

最後乙個, d[9] = 7,它在b[3] = 4和b[4] = 8之間,所以我們知道,最新的b[4] =7,b[1..5] = 1, 3, 4, 7, 9,這時的len = 5。

於是我們知道了lis的長度為5。

注意。這個1,3,4,7,9不是lis,它只是儲存的對應長度lis的最小末尾。有了這個末尾,我們就可以乙個乙個地插入資料。雖然最後乙個d[9] = 7更新進去對於這組資料沒有什麼意義,但是如果後面再出現兩個數字 8 和 9,那麼就可以把8更新到d[5], 9更新到d[6],得出lis的長度為6。

最後我們發現:在b中插入資料是有序的,而且是進行替換而不需要挪動——也就是說,我們可以使用二分查詢,將每乙個數字的插入時間優化到o(logn)~~~~~於是演算法的時間複雜度就降低到了o(nlogn)!

一般的情況下(o(n^2)):

**(1):

狀態設計:f[i]代表以a[i]結尾的lis的長度

狀態轉移:f[i]=max(1<=j< i,a[j]< a[i])

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

時間複雜度:o(n^2)

#include #include #include #include #include #include using namespace std;

const int maxn = 103,inf=0x7f7f7f7f;

int a[maxn],f[maxn];

int n,ans=-inf;

int main()

for(int i=1;i<=n;i++)

for(int j=1;j**(2):

#include #include #include using namespace std;

int a[1007],dp[1007],n;

int lis(int *a)

return ans;

}

二分優化(o(nlogn)):

**(1):

#include #include #include using namespace std;

int a[40007], dp[40007], n;

int bin(int len,int k)

return l;}

int lis(int *a)

return ans;

}

**(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)。

#include #include #include #include #include #include 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的位置

return l;

}int main()

low[1]=a[1];

ans=1;//初始時lis長度為1

for(int i=2;i<=n;i++)

printf("%d\n",ans);//輸出答案

return 0;

}

樹狀陣列維護(o(nlogn)):

我們再來回顧o(n^2)dp的狀態轉移方程:f[i]=max(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陣列已經是有序的,所以可以直接更新。有點繞,具體看**。

#include #include #include #include #include #include using namespace std;

const int maxn =103,inf=0x7f7f7f7f;

struct nodez[maxn];

int t[maxn];

int n;

bool cmp(node a,node b)

{ return a.val==b.val?a.num**:

最長上公升子串行LIS模板

有兩種演算法複雜度為 o n logn 和 o n 2 o n 2 演算法分析如下 a 1 a n 存的都是輸入的數 1 對於a n 來說.由於它是最後乙個數,所以當從a n 開始查詢時,只存在長度為1的上公升子串行 2 若從a n 1 開始查詢.則存在下面的兩種可能性 1 若a n 1 a n 則...

最長上公升子串行 LIS 模板

最長遞增 上公升 子串行問題 在一列數中尋找一些數,這些數滿足 任意兩個數a i 和a j 若i考慮兩個數a x 和a y x y且a x 按dp t k來分類,只需保留dp t k的所有a t 中的最小值,設d k 記錄這個值,d k min。這時注意到d的兩個特點 重要 1.d k 在計算過程中...

最長上公升子串行 LIS

題目 兩道題幾乎一樣,只不過對於輸入輸出的要求有所不同罷了。lis有兩種方法 一 第一種方法 時間複雜度為o n 2 狀態 dp i 區間為0 i的序列的lis 轉移方程 dp i max 1,dp k 1 0 k include include include include using name...