動(dòng)態(tài)規(guī)劃——線性DP
最長(zhǎng)不下降序列(LIS)
暴力搜索:由可行的所有起點(diǎn)出發(fā),搜索出所有的路徑。
但是深搜的算法時(shí)間復(fù)雜度要達(dá)到 O ( 2 n ) O(2^n) O(2n) (每個(gè)數(shù)都有選或不選的兩個(gè)選擇),指數(shù)級(jí)的時(shí)間復(fù)雜度在本題中( n ≤ 100 n≤100 n≤100)顯然是不能接受的。那么再觀察這個(gè)這棵遞歸樹,可以發(fā)現(xiàn)其中有很多重復(fù)的地方。
那么如何優(yōu)化呢?
首先可以使用數(shù)組將重復(fù)的部分記錄下來,此后遇到相同的狀態(tài)直接引用已經(jīng)記錄在數(shù)組中的數(shù)據(jù)即可,這樣的方法叫做記憶化搜索,也叫剪枝(后面我們?cè)偌?xì)講)。
所以,如果按照上面的思路將需要計(jì)算的部分用數(shù)組記錄,那么就可以省略那些重復(fù)的部分,所以最終我們需要計(jì)算的就只剩下以從 1 1 1 到 n n n 的每個(gè)點(diǎn)為起點(diǎn)的最長(zhǎng)不下降序列。
以序列 1 , 5 , 2 , 4 , 3 1, 5, 2, 4, 3 1,5,2,4,3 為例:
首先將以每個(gè)點(diǎn)為起點(diǎn)的所有長(zhǎng)度都初始化為 1 1 1,所有下一步都初始化為 0 0 0。
起點(diǎn)下標(biāo) | 起點(diǎn)數(shù)值 | 最長(zhǎng)不下降序列的長(zhǎng)度 | 最長(zhǎng)不下降序列的起點(diǎn)的下一步的下標(biāo) |
---|---|---|---|
5 5 5 | 3 3 3 | l e n ( 3 ) = 1 len(3)=1 len(3)=1 | 0 0 0 |
4 4 4 | 4 4 4 | l e n ( 4 ) = 1 len(4)=1 len(4)=1 | 0 0 0 |
3 3 3 | 2 2 2 | l e n ( 2 ) = m a x ( l e n ( 4 ) , l e n ( 3 ) ) + 1 = 2 len(2)=max(len(4), len(3))+1=2 len(2)=max(len(4),len(3))+1=2 | 4 / 5 4/5 4/5 |
2 2 2 | 5 5 5 | l e n ( 5 ) = 1 len(5)=1 len(5)=1 | 0 0 0 |
1 1 1 | 1 1 1 | l e n ( 1 ) = m a x ( l e n ( 5 ) , l e n ( 2 ) , l e n ( 4 ) , l e n ( 3 ) ) + 1 = 3 len(1)=max(len(5), len(2), len(4), len(3))+1=3 len(1)=max(len(5),len(2),len(4),len(3))+1=3 | 3 3 3 |
綜上,算法分析:
根據(jù)動(dòng)態(tài)規(guī)劃的原理,由后往前進(jìn)行搜索(當(dāng)然從前往后也一樣)。
-
對(duì) b ( n ) b(n) b(n) 來說,由于它是最后一個(gè)數(shù),所以當(dāng)從 b ( n ) b(n) b(n) 開始查找時(shí),只存在長(zhǎng)度為 1 1 1 的不下降序列;
-
若從 b ( n ? 1 ) b(n-1) b(n?1) 開始查找,則存在下面的兩種可能性:
①若 b ( n ? 1 ) < b ( n ) b(n-1)<b(n) b(n?1)<b(n) ,則存在長(zhǎng)度為 2 2 2 的不下降序列 b ( n ? 1 ) b(n-1) b(n?1), b ( n ) b(n) b(n)。
②若 b ( n ? 1 ) > b ( n ) b(n-1)>b(n) b(n?1)>b(n) ,則存在長(zhǎng)度為 1 1 1 的不下降序列 b ( n ? 1 ) b(n-1) b(n?1) 或 b ( n ) b(n) b(n)。
-
一般若從 b ( i ) b(i) b(i) 開始,此時(shí)最長(zhǎng)不下降序列應(yīng)該按下列方法求出:
在 b ( i + 1 ) , b ( i + 2 ) , … , b ( n ) b(i+1),b(i+2),…,b(n) b(i+1),b(i+2),…,b(n) 中,找出一個(gè)比 b ( i ) b(i) b(i) 大的且最長(zhǎng)的不下降序列,作為它的后繼。
數(shù)據(jù)結(jié)構(gòu):
為算法上的需要,定義一個(gè)整數(shù)類型二維數(shù)組 b ( N , 3 ) b(N,3) b(N,3)
- b ( i , 1 ) b(i,1) b(i,1) 表示第 i i i 個(gè)數(shù)的數(shù)值本身;
- b ( i , 2 ) b(i,2) b(i,2) 表示從 i i i 位置到達(dá) N N N 的最長(zhǎng)不下降序列長(zhǎng)度;
- b ( i , 3 ) b(i,3) b(i,3) 表示從 i i i 位置開始最長(zhǎng)不下降序列的下一個(gè)位置,若 b [ i , 3 ] = 0 b[i,3]=0 b[i,3]=0 則表示后面沒有連接項(xiàng)。
#include <iostream>
using namespace std;
int b[107][7];
int main()
{
int n=0;
while(cin>>b[++n][1]) //b[i][1]表示第i個(gè)數(shù)本身
{
b[n][2]=1; //b[i][2]表示以第i個(gè)數(shù)為起點(diǎn)的最長(zhǎng)不下降序列的長(zhǎng)度
b[n][3]=0; //b[i][3]表示以第i個(gè)數(shù)為起點(diǎn)的最長(zhǎng)不下降序列的起點(diǎn)下一步
}
int start=0;
for(int i=n-1; i; --i)
{
int len=0, next=0; //用來記錄以當(dāng)前第i個(gè)點(diǎn)為起點(diǎn)的最長(zhǎng)不下降序列的長(zhǎng)度和下一步的下標(biāo)
for(int j=i+1; j<=n; ++j)
if(b[i][1]<b[j][1] && b[j][2]>len) //滿足這樣的兩個(gè)條件才能
len=b[j][2], next=j;
if(len) b[i][2]=len+1, b[i][3]=next;
if(b[i][2]>b[start][2]) start=i; //找到長(zhǎng)度最大的不下降序列,并記錄當(dāng)前序列的起點(diǎn)
}
cout<<b[start][2]<<endl;
while(start)
{
cout<<b[start][1]<<" ";
start=b[start][3];
}
return 0;
}
最長(zhǎng)上升序列(LIS)模板:
簡(jiǎn)化題意:求一個(gè)序列的不下降序列的最大長(zhǎng)度。
代碼 1 1 1(枚舉起點(diǎn))
//枚舉起點(diǎn)(從后往前枚舉)
#include <iostream>
using namespace std;
const int N=1e3+7;
int a[N], f[N]; //f[i]表示以第i個(gè)數(shù)為起點(diǎn)的最長(zhǎng)不下降序列的長(zhǎng)度
int main()
{
int n;
cin>>n;
for(int i=1; i<=n; ++i)
cin>>a[i], f[i]=1; //將所有值初始化為1
int ans=1;
for(int i=n-1; i; --i) //從后往前枚舉
{
for(int j=i+1; j<=n; ++j)
if(a[i]<a[j] && f[j]+1>f[i])
f[i]=f[j]+1;
ans=max(ans, f[i]);
}
cout<<ans;
return 0;
}
代碼 2 2 2(枚舉終點(diǎn))
//枚舉終點(diǎn)(從前往后枚舉)
#include <iostream>
using namespace std;
const int N=1e3+7;
int a[N], f[N]; //f[i]表示以第i個(gè)數(shù)為終點(diǎn)的最長(zhǎng)不下降序列的長(zhǎng)度
int main()
{
int n;
cin>>n;
for(int i=1; i<=n; ++i)
cin>>a[i], f[i]=1;
int ans=1;
for(int i=2; i<=n; ++i) //從前往后枚舉
{
for(int j=1; j<i; ++j)
if(a[j]<a[i] && f[j]+1>f[i])
f[i]=f[j]+1;
ans=max(ans, f[i]);
}
cout<<ans;
return 0;
}
模板優(yōu)化
上述代碼的時(shí)間復(fù)雜度為 O ( n 2 ) O(n^2) O(n2) ,所以如果把數(shù)據(jù)范圍改為 n ≤ 100000 n≤100000 n≤100000 也會(huì)出現(xiàn)超時(shí)的情況,所以在這里對(duì)于求最長(zhǎng)不下降序列長(zhǎng)度的問題還可以進(jìn)一步優(yōu)化。在這里對(duì)上述代碼 2 2 2 的思路進(jìn)一步思考。(優(yōu)化的過程其實(shí)就是一個(gè)去除冗余的過程。)
用以下樣例作為引子:
Input:
7
3 1 2 1 8 5 6
Output:
4
枚舉每一個(gè)點(diǎn)作為終點(diǎn),從第一個(gè)數(shù) 3 3 3 開始, 3 3 3 可以作為一個(gè)長(zhǎng)度為 1 1 1 的最長(zhǎng)不下降序列,接著到第二個(gè)數(shù) 1 1 1,也是一個(gè)長(zhǎng)度為 1 1 1 的長(zhǎng)度為 1 1 1 的最長(zhǎng)不下降序列……,對(duì)于后面的每個(gè)數(shù),如果它可以接到 3 3 3 的后面,那么它一定可以接到 1 1 1 的后面,所以以 3 3 3 為結(jié)尾長(zhǎng)度為 1 1 1 的最長(zhǎng)不下降序列就沒有必要存下來了,因?yàn)? 1 1 1 比 3 3 3 更優(yōu)(后面可以接的數(shù)范圍更廣,即更小的數(shù)值作為結(jié)尾更優(yōu))。所以我們也沒有必要將所有長(zhǎng)度為 1 1 1 的序列都存下來,只需要每次存下那個(gè)相同長(zhǎng)度下最優(yōu)的情況的結(jié)尾數(shù)值即可。
那么對(duì)于當(dāng)前的不下降子序列的長(zhǎng)度 i i i,一定有一個(gè)結(jié)尾值 f [ i ] f[i] f[i],則有
結(jié)論:對(duì)于不同長(zhǎng)度的序列的結(jié)尾數(shù)值一定是隨著序列長(zhǎng)度的增加而嚴(yán)格單調(diào)遞增的。
證明(反證法):
假設(shè)存在長(zhǎng)度為 i i i 的序列結(jié)尾數(shù)值 f [ i ] f[i] f[i] 小于或等于長(zhǎng)度為 i ? 1 i-1 i?1 的序列結(jié)尾數(shù)值 f [ i ? 1 ] f[i-1] f[i?1] ,即 f [ i ] ≤ f [ i ? 1 ] f[i]≤f[i-1] f[i]≤f[i?1] 。
由于每個(gè)序列都是一個(gè)不下降序列,所以當(dāng)前長(zhǎng)度為 i i i 的序列的倒數(shù)第二個(gè)數(shù) x x x 一定滿足關(guān)系: x ≤ f [ i ] ≤ f [ i ? 1 ] x≤f[i]≤f[i-1] x≤f[i]≤f[i?1]。但若如此,在前面遍歷的時(shí)候,對(duì)于長(zhǎng)度為 i ? 1 i-1 i?1 的序列的結(jié)尾更優(yōu)的結(jié)果應(yīng)該選擇的是比當(dāng)前 f [ i ? 1 ] f[i-1] f[i?1] 更小的 x x x,所以這與我們最終選擇的 f [ i ? 1 ] f[i-1] f[i?1] 相悖。
所以假設(shè)(存在長(zhǎng)度為 i i i 的序列結(jié)尾數(shù)值 f [ i ] f[i] f[i] 小于或等于長(zhǎng)度為 i ? 1 i-1 i?1 的序列結(jié)尾數(shù)值 f [ i ? 1 ] f[i-1] f[i?1] ,即 f [ i ] ≤ f [ i ? 1 ] f[i]≤f[i-1] f[i]≤f[i?1])不成立。
所以對(duì)于不同長(zhǎng)度的序列的結(jié)尾數(shù)值一定是隨著序列長(zhǎng)度的增加而嚴(yán)格單調(diào)遞增的結(jié)論成立。
有了這樣的前提,如果想求以 a [ i ] a[i] a[i] 結(jié)尾的最長(zhǎng)不下降子序列的長(zhǎng)度應(yīng)該如何解決呢?
屆時(shí),只需要找到一個(gè)最大的比 a [ i ] a[i] a[i] 小的數(shù) f [ j ] f[j] f[j],并將 a [ i ] a[i] a[i] 接到其后面即可,這樣所得到的以 a [ i ] a[i] a[i] 為結(jié)尾的最長(zhǎng)不下降子序列的長(zhǎng)度就是 j + 1 j+1 j+1。這時(shí)還需要更新一下, f [ j + 1 ] f[j+1] f[j+1],因?yàn)殚L(zhǎng)度為 j + 1 j+1 j+1 的序列的結(jié)尾 a [ i ] a[i] a[i] 一定是小于先前的長(zhǎng)度為 j + 1 j+1 j+1 的序列的結(jié)尾數(shù)的。
那么這個(gè)過程中如何找到這個(gè)最大的比 a [ i ] a[i] a[i] 小的數(shù)呢?肯定不能選擇逐個(gè)遍歷這樣的方法的,因?yàn)檫@樣的時(shí)間復(fù)雜度就又回到了優(yōu)化前的 O ( n 2 ) O(n^2) O(n2)。
這時(shí)就用到前面的結(jié)論了:對(duì)于不同長(zhǎng)度的序列的結(jié)尾數(shù)值一定是隨著序列長(zhǎng)度的增加而嚴(yán)格單調(diào)遞增的結(jié)論成立。既然是嚴(yán)格單調(diào)上升的序列,尋找其中一個(gè)數(shù),我們就可以想到使用二分法。
簡(jiǎn)單介紹二分法(具體我們后面會(huì)在學(xué)習(xí)分治思想的時(shí)候繼續(xù)講):二分查找也稱折半查找,顧名思義,就是每次查找去掉不符合條件的一半?yún)^(qū)間,直到找到答案(整數(shù)二分)或者和答案十分接近(浮點(diǎn)二分)。
#include <iostream>
using namespace std;
const int N=1e5+7;
int a[N], f[N]; //f[i]表示長(zhǎng)度為i的最長(zhǎng)不下降子序列的最后一個(gè)值
int main()
{
int n;
cin>>n;
for(int i=1; i<=n; ++i) cin>>a[i];
int len=0;
f[1]=-2e9;
for(int i=1; i<=n; ++i)
{
int l=0, r=len;
while(l<r)
{
int mid=l+r+1>>1;
if(f[mid]<a[i]) l=mid;
else r=mid-1;
}
len=max(len, r+1);
f[r+1]=a[i];
}
cout<<len;
return 0;
}
最長(zhǎng)公共子序列(LCS)
狀態(tài)表示:
設(shè) f [ i ] [ j ] f[i][j] f[i][j] 表示 s 1 s1 s1 的前 i i i 個(gè)字符與 s 2 s2 s2 的前 j j j 個(gè)字符的最大公共子序列的長(zhǎng)度了
設(shè)字符串 s 1 s1 s1 和 s 2 s2 s2 的長(zhǎng)度分別為 l e n 1 len1 len1 和 l e n 2 len2 len2,則 s 1 s1 s1 和 s 2 s2 s2 的最大公共子序列的長(zhǎng)度為 f [ l e n 1 ] [ l e n 2 ] f[len1][len2] f[len1][len2]
狀態(tài)初始化:
f [ l e n 1 ] [ 0 ] = 0 f[len1][0]=0 f[len1][0]=0, f [ 0 ] [ l e n 2 ] = 0 f[0][len2]=0 f[0][len2]=0
狀態(tài)轉(zhuǎn)移:(分為三種情況討論)
s 1 [ i ] s1[i] s1[i] 不在公共子序列中:該情況下 f [ i ] [ j ] = f [ i ? 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i?1][j]
s 2 [ j ] s2[j] s2[j] 不在公共子序列中:該情況下 f [ i ] [ j ] = f [ i ] [ j ? 1 ] f[i][j]=f[i][j-1] f[i][j]=f[i][j?1]
s 1 [ i ] s1[i] s1[i] 和 s 2 [ j ] s2[j] s2[j] 都在公共子序列中(且 s 1 [ i ] = s 2 [ j ] s1[i]=s2[j] s1[i]=s2[j]):該情況下 f [ i ] [ j ] ? f [ i ? 1 ] [ j ? 1 ] + 1 f[i][j]-f[i-1][j-1]+1 f[i][j]?f[i?1][j?1]+1
f [ i ] [ j ] f[i][j] f[i][j] 取上述三種情況的最大值(第三種情況要求 s 1 [ i ] = s 2 [ j ] s1[i]=s2[j] s1[i]=s2[j])
以 s 1 = B D C A B A s1=BDCABA s1=BDCABA 和 s 2 = A B C B D A B s2=ABCBDAB s2=ABCBDAB 兩個(gè)字符串為例:
A | B | C | B | D | A | B | |||
---|---|---|---|---|---|---|---|---|---|
s 1 s1 s1\ s 2 s2 s2 | 〇 | ① | ② | ③ | ④ | ⑤ | ⑥ | ⑦ | |
〇 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
B | ① | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
D | ② | 0 | 0 | 1 | 1 | 1 | 2 | 2 | 2 |
C | ③ | 0 | 0 | 1 | 2 | 2 | 2 | 2 | 2 |
A | ④ | 0 | 1 | 1 | 2 | 2 | 2 | 3 | 3 |
B | ⑤ | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 |
A | ⑥ | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 |
代碼
#include <iostream>
#include <cstring>
using namespace std;
const int N=207;
char s1[N], s2[N];
int f[N][N];
int main()
{
scanf("%s%s", s1+1, s2+1); //注意將下標(biāo)為0的位置空出來,避免后面的狀態(tài)
for(int i=1; s1[i]; ++i)
for(int j=1; s2[j]; ++j)
if(s1[i]==s2[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i][j-1], f[i-1][j]);
int len1=strlen(s1+1), len2=strlen(s2+1);
cout<<f[len1][len2];
return 0;
}
最長(zhǎng)公共上升子序列(LCIS)
樸素
首先按照上述思路用代碼實(shí)現(xiàn)出來:
#include <iostream>
using namespace std;
const int N=3e3+7;
int a[N], b[N], f[N][N];
//f[i][j]表示所有在a[1~i]和b[1~j]中都出現(xiàn)過
//且以b[j]結(jié)尾的公共上升子序列的集合
int main()
{
int n;
cin>>n;
for(int i=1; i<=n; ++i) cin>>a[i];
for(int i=1; i<=n; ++i) cin>>b[i];
for(int i=1; i<=n; ++i)
for(int j=1; j<=n; ++j)
{
f[i][j]=f[i-1][j]; //a[i]!=b[j](以b[j]結(jié)尾且不包含a[i])
if(a[i]==b[j])
{
int mx=0;
for(int k=1; k<j; ++k) //遍歷找出b[j]前一個(gè)是由哪個(gè)狀態(tài)轉(zhuǎn)移過來的
if(b[k]<b[j]) //滿足“上升”條件
mx=max(f[i-1][k]+1, mx); //加上的1就是當(dāng)前的b[j]
f[i][j]=max(f[i][j], mx);
}
}
int ans=0;
for(int i=1; i<=n; ++i)
ans=max(f[n][i], ans);
cout<<ans;
return 0;
}
優(yōu)化
上述代碼一個(gè)明顯的弊端在于它的三層循環(huán),時(shí)間復(fù)雜度達(dá)到了 O ( n 3 ) O(n^3) O(n3)。那么如何優(yōu)化呢?顯然可以從最內(nèi)層的循環(huán)下手。
for(int k=1; k<j; ++k)
if(b[k]<b[j])
mx=max(f[i-1][k]+1, mx);
這里有一個(gè)小細(xì)節(jié)的處理:當(dāng)前循環(huán)是在 a [ i ] = = b [ j ] a[i]==b[j] a[i]==b[j] 的條件下的,所以循環(huán)中的判斷條件 b [ k ] < b [ j ] b[k]<b[j] b[k]<b[j] 也可以改成 b [ k ] < a [ i ] b[k]<a[i] b[k]<a[i],即:
for(int k=1; k<j; ++k)
if(b[k]<a[i])
mx=max(f[i-1][k]+1, mx);
思考:該循環(huán)的目的是什么呢?
每個(gè) b [ k ] b[k] b[k] 都對(duì)應(yīng)一個(gè)狀態(tài) f [ i ? 1 , k ] f[i-1,k] f[i?1,k]。那么當(dāng)前循環(huán)相當(dāng)于是在 b [ 1 ] , b [ 2 ] , b [ 3 ] , . . . , b [ j ? 1 ] b[1], b[2], b[3],...,b[j-1] b[1],b[2],b[3],...,b[j?1] 中找出所有小于 a [ i ] a[i] a[i] 的數(shù),并在比較他們分別對(duì)應(yīng)的 f [ i ? 1 , k ] f[i-1,k] f[i?1,k] 之后選出一個(gè)最大值,作為當(dāng)前狀態(tài)的上一個(gè)狀態(tài)。這些狀態(tài)的大小都是固定不變的,所以在當(dāng)前一輪中我們比較了 f [ i ? 1 , 1 ] , f [ i ? 1 , 2 ] , f [ i ? 1 , 3 ] , . . . , f [ i ? 1 , j ? 1 ] f[i-1, 1], f[i-1, 2], f[i-1, 3],..., f[i-1, j-1] f[i?1,1],f[i?1,2],f[i?1,3],...,f[i?1,j?1] 之后,在下一輪比較 f [ i ? 1 , 1 ] , f [ i ? 1 , 2 ] , f [ i ? 1 , 3 ] , . . . , f [ i ? 1 , j ? 1 ] , f [ i ? 1 ] [ j ] f[i-1, 1], f[i-1, 2], f[i-1, 3],...,f[i-1, j-1], f[i-1][j] f[i?1,1],f[i?1,2],f[i?1,3],...,f[i?1,j?1],f[i?1][j] 的時(shí)候又將前面的這些拿過來重復(fù)對(duì)比了一次,所以這里多了很多重復(fù)的部分。那么想要將這層循環(huán)優(yōu)化掉,則可以直接比較當(dāng)前 b [ j ] b[j] b[j] 和 a [ i ] a[i] a[i] 即可:只有 a [ i ] = b [ j ] a[i]=b[j] a[i]=b[j] 的時(shí)候才更新當(dāng)前的 f [ i ] [ j ] f[i][j] f[i][j],只有當(dāng) b [ j ] < a [ i ] b[j]<a[i] b[j]<a[i] 的時(shí)候我們才要去更新前綴的最大值。
如圖示:當(dāng)前被?的所有數(shù),在之后比較中還會(huì)被再拿出來進(jìn)行比較。
#include <iostream>
using namespace std;
const int N=3e3+7;
int a[N], b[N], f[N][N];
int main()
{
int n;
cin>>n;
for(int i=1; i<=n; ++i) cin>>a[i];
for(int i=1; i<=n; ++i) cin>>b[i];
for(int i=1; i<=n; ++i)
{
int mx=1;
for(int j=1; j<=n; ++j)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j]) f[i][j]=max(mx, f[i][j]); //更新f[i][j]這個(gè)狀態(tài)值
else if(b[j]<a[i]) mx=max(mx, f[i-1][j]+1); //更新前綴的最大值
}
}
int ans=0;
for(int i=1; i<=n; ++i)
ans=max(f[n][i], ans);
cout<<ans;
return 0;
}
再優(yōu)化
將狀態(tài)數(shù)組由二維壓縮成一維:
#include <iostream>
using namespace std;
const int N=3e3+7;
int a[N], b[N], f[N];
int main()
{
int n;
cin>>n;
for(int i=1; i<=n; ++i) cin>>a[i];
for(int i=1; i<=n; ++i) cin>>b[i];
for(int i=1; i<=n; ++i)
{
int mx=1;
for(int j=1; j<=n; ++j)
if(a[i]==b[j])
f[j]=max(mx, f[j]);
else if(b[j]<a[i])
mx=max(mx, f[j]+1);
}
int ans=0;
for(int i=1; i<=n; ++i)
ans=max(f[i], ans);
cout<<ans;
return 0;
}
攔截導(dǎo)彈(missile)
該問題的第一問是要求一個(gè)最長(zhǎng)不上升序列的長(zhǎng)度,典型的LIS問題。
第二問用貪心的思路做:
貪心流程:從前往后掃描每個(gè)數(shù),對(duì)于每個(gè)數(shù):
- 情況 1 1 1:如果現(xiàn)有的子序列的結(jié)尾都小于當(dāng)前數(shù),則創(chuàng)建新的子序列
- 情況 2 2 2:將當(dāng)前數(shù)放到結(jié)尾大于等于它的最小子序列后面
設(shè) A A A 為貪心算法得到的序列個(gè)數(shù); B B B 表示最優(yōu)解;如果能證明 A ≥ B A≥B A≥B 并且 A ≤ B A≤B A≤B,那么就可以得到 A = B A=B A=B。
因?yàn)樽顑?yōu)解一定是最少的答案,所以貪心算法的結(jié)果一定大于等于最優(yōu)解,即 A ≥ B A≥B A≥B;
那么接下來如何證明 A ≤ B A≤B A≤B 呢?可以使用調(diào)整法:假設(shè)最優(yōu)解對(duì)應(yīng)的方案數(shù)與貪心算法算出當(dāng)前的方案不同。
找到第一個(gè)不同的數(shù):假設(shè)當(dāng)前貪心算法找到的方案應(yīng)該將當(dāng)前的
x
x
x 接到
a
a
a 的后面,即
a
a
a 是大于等于
x
x
x 的最小子序列的結(jié)尾;而最優(yōu)解這個(gè)時(shí)候是將
x
x
x 接到
b
b
b 的后面,由于
a
a
a 是大于等于
x
x
x 的最小數(shù),所以一定有
b
≥
a
b≥a
b≥a,那接下來兩種結(jié)果引出來的序列,是可以進(jìn)行交換的(因?yàn)?
x
≤
a
≤
b
x≤a≤b
x≤a≤b,所以
x
x
x 可以接在
a
a
a 后面,也可以接在
b
b
b 后面),調(diào)換后的結(jié)果并不影響最終的序列個(gè)數(shù)。
所以當(dāng)前方案即使不同,貪心算法最終算出來的方案數(shù)也是與最優(yōu)解的方案數(shù)是相同的。所以當(dāng)前貪心算法就是最優(yōu)算法。
代碼 1 1 1
#include <iostream>
using namespace std;
const int N=1e3+7;
int n, a[N], f[N], g[N];
int main()
{
while(cin>>a[++n]);
int res=0;
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);
res=max(res, f[i]);
}
cout<<res<<endl;
int cnt=0;
for(int i=1; i<=n; ++i)
{
int k=0;
while(k<cnt && g[k]<a[i]) k++;
g[k]=a[i];
if(k>=cnt) cnt++;
}
cout<<cnt<<endl;
return 0;
}
對(duì)于一個(gè)序列,最少用到的可將其覆蓋的非上升子序列的個(gè)數(shù)與最大上升子序列的長(zhǎng)度是相同的,這兩個(gè)問題是一個(gè)對(duì)偶問題(離散數(shù)學(xué)中的反鏈定理Dilworth定理)。
所采用的做法完全相同 = > => => 最終求得的結(jié)果完全相同 = > => => 解決的問題完全相同文章來源:http://www.zghlxwxcb.cn/news/detail-803157.html
代碼 2 2 2文章來源地址http://www.zghlxwxcb.cn/news/detail-803157.html
#include <iostream>
using namespace std;
const int N=1e3+7;
int a[N], f1[N], f2[N];
int main()
{
int n=0, ans1=0, ans2=0;
while(scanf("%d", &a[++n])!=EOF) f2[n]=1;
for(int i=1; i<=n; i++)
{
for(int j=1; j<i; j++)
{
if(a[j]>=a[i]) f1[i]=max(f1[i], f1[j]+1);
else f2[i]=max(f2[i], f2[j]+1);
}
ans1=max(ans1, f1[i]);
ans2=max(ans2, f2[i]);
}
printf("%d\n%d", ans1, ans2);
return 0;
}
到了這里,關(guān)于動(dòng)態(tài)規(guī)劃——線性DP的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!