動態規划:最長上升子序列
碎碎念
前天復習dp時學習了一遍,本來覺得太簡單了,沒想寫的,結果今天周賽的第四題直接給了道模板題,我還沒默出來,罰了5分鍾。趕緊復習一下
學習了加速cin的方法,怕忘了,先寫在這里
ios::sync_with_stdio(false);
正文
Longest Increasing Subsequence,簡稱LIS。
給一串數字,找出子序列中最長的上升序列。
基礎解法:
dp[i]表示以i結尾的最長上升子序列的長度,可以得到以下狀態轉移方程:
\[\left\{ \begin{matrix} dp[i]=max(dp[j])+1,(j<i,a[j]<a[i]) \\ dp[i]>=1(i\in N) \end{matrix} \right. \]
dp[i]從前項推導得出,遍歷 1……i-1 ,找到比a[i]小,並且dp最大的j,在此基礎上加一,即為模擬上升子序列的添加。
以導彈模擬為例題,此題分別要求求出最長非升子序列和最長升序子序列:
#include<iostream>
using namespace std;
int a[1 << 20];
int before[1 << 20];
int len[1 << 20];
int last[1 << 20], tot = 0, ans = 0;
int main() {
int n = 0;
while (cin >> a[++n]) {
len[n] = 1;
int distance = 1<<20;
int mini = 1;
for (int i = 1; i <= tot; i++) {
if (a[n] <= last[i] && last[i] - a[n] < distance)
{
distance = last[i] - a[n];
mini = i;
}
}
if (distance == 1<<20) last[++tot] = a[n];
else last[mini] = a[n];
for (int i = n - 1; i >= 1; i--) {
if (a[n] <= a[i]) if (len[n] <= len[i])
len[n] = len[i] + 1;
}
}
int maxi = 1;
for (int i = 1; i <= n; i++) if (len[i] > len[maxi]) maxi = i;
cout << len[maxi] << endl;
cout << tot;
}
此種解法的時間復雜度為O(n²),不足以解決1e5規模的問題,需要優化
優化解法
這篇博客寫的非常好。參考了一下。
工具介紹
引入了STL的lower_bound與up_bound。兩者用法基本一致:
\[lower\_bound(首地址,末地址,查找元素,查找規則(可以參省)); \]
在順序序列里,查找的是第一個大於等於該元素的元素(upper_bound沒有等於),返回值是地址,如果要轉換成下標,減去首地址即可:
\[\pmb{int}\ p = lower\_bound(a+1,a+n+1,find) \pmb{- a}; \]
查找默認是升序序列,如果要在降序序列里查找,需要在查找規則里寫上greater<int>()。
方案執行
為得到最長上升子序列,假設一個數組d,同時用top記錄數組的末元素。
- 如果a[i] > d[top],那么就把a[i]加到d的末端,更新top,這樣,保證d是一條單調上升的序列。
(此時top代表着a[i]結尾的最長升序列的長度) - 如果a[i] <= d[top],那么就用lower_bound在d中找到小於等於a[i]的元素下標p,用a[i]替換掉d[p]。
(此時p代表這a[i]結尾的最長升序列的長度)
為什么成立?
- 在我們用a[i]更新的時,無論是哪種情況,我們都保證d前面的數組的元素排在a[i]前面。
- 每次遇到第二種情況時,我們都是選擇用現有的元素將原有元素進行替代,而不是插入,這樣保證了最長序列的長度是不變的,同時降低原有元素的值,優化了序列的質量:本來就能放后面的值,還是能,本來放不了的值,現在降低了門檻,能替代,這樣,最后降低了末尾元素的值,就可以使序列保證整體最長,元素最小
由於替代操作改變了a數組元素的位置,因此,i前面的元素並不是按照a順序排列的,是按照大小排列的,d數組的元素d[i]代表的不是以d[i]結尾的序列是d[1……i],而是代表着當a[j]更新到d[i]后,以a[j]結尾的序列的lis是i。
理論先寫到這里,依然是導彈攔截為例題,代碼如下:
#include<iostream>
#include<algorithm>
using namespace std;
int great[1010];
int les[1010];
int main() {
ios::sync_with_stdio(false);
int t , top1 = 0 , top2 = 0;
cin >> t;
great[++top1] = t;
les[++top2] = t;
while(cin >> t) {
if( t > great[top1] )
great[++top1] = t;
else {
int p = lower_bound(great+1 , great+top1+1 , t) - great;
great[p] = t;
}
if( t <= les[top2] )
les[++top2] = t;
else {
int p = upper_bound(les+1 , les+top2+1, t,greater<int>()) - les;
les[p] = t;
}
}
cout << top2 << " " << top1;
}
