C語言深入探究動(dòng)態(tài)規(guī)劃之線性DP
寫在前面
之前講過背包問題,不知道大家忘了嗎,如果忘了可以點(diǎn)這里,這次是線性DP
數(shù)字三角形
狀態(tài)表示:f[i,j],到點(diǎn)i,j的最大路徑
狀態(tài)計(jì)算:f[i,j] = MAX(f[i-1,j-1]+a[i,j],f[i-1,j]+a[i,j])
看圖,以例題為例,將它看成五行五列的三角形,每個(gè)點(diǎn)都可以用坐標(biāo)表示。那么我們可以得知到一個(gè)數(shù)的最大路徑要么來自左上,要么來自右上。左上的數(shù)用f[i-1,j-1]表示,右上的數(shù)f[i-1,j]表示,因此我們就有了狀態(tài)轉(zhuǎn)移公式:
f[i,j] = MAX(f[i-1,j-1]+a[i,j],f[i-1,j]+a[i,j])
所以就有了最終的代碼:
#include <iostream> #include <algorithm> using namespace std; const int N = 510, INF = 1e9; int n; int a[N][N]; int f[N][N]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= i; j ++ ) scanf("%d", &a[i][j]); for (int i = 0; i <= n; i ++ ) for (int j = 0; j <= i + 1; j ++ )//注意這里j從0到i+1,因?yàn)閷?duì)于邊界點(diǎn),它的上一層只有一條路徑通向它 f[i][j] = -INF;//初始化近似為-∞ f[1][1] = a[1][1]; for (int i = 2; i <= n; i ++ ) for (int j = 1; j <= i; j ++ ) f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]); int res = -INF; for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]); printf("%d\n", res); return 0; }
最長上升子序列
狀態(tài)表示:f[i]表示從第一個(gè)數(shù)字開始算,以w[i]結(jié)尾的最大的上升序列。(以w[i]結(jié)尾的所有上升序列中屬性為最大值的那一個(gè))
狀態(tài)計(jì)算(集合劃分):j∈(0,1,2,…,i-1), 在w[i] > w[j]時(shí),
f[i] = max(f[i], f[j] + 1)。
有一個(gè)邊界,若前面沒有比i小的,f[i]為1(自己為結(jié)尾)。
最后在找f[i]的最大值。
時(shí)間復(fù)雜度
O(n2) 狀態(tài)數(shù)(n) * 轉(zhuǎn)移數(shù)(n)
看圖, 首先 f[i]f[i] 的含義是以 w[i]結(jié)尾的最長上升子序列的長度
初始值 f[i]=1,i∈[0,n−1],表示自己就是最長上升子序列,長度為 1
接下來考慮狀態(tài)轉(zhuǎn)移,把前 i−1個(gè)數(shù)字中所有滿足條件 w[j]<w[i](因?yàn)橐笫巧仙有蛄校?的 j 找出來,那么 f[i] 就可以試著更新為以 w[j] 結(jié)尾的最長上升子序列的長度 再加上 自己的長度 1,但可能更新完的結(jié)果沒有之前更新過的 f[i] 大,最后兩者取一個(gè) max,所以狀態(tài)轉(zhuǎn)移方程就是 f[i]=max(f[i],f[j]+1)
#include <iostream> using namespace std; const int N = 1010; int n; int w[N], f[N]; int main() { cin >> n; for (int i = 0; i < n; i++) cin >> w[i]; int mx = 1; // 找出所計(jì)算的f[i]之中的最大值,邊算邊找 for (int i = 0; i < n; i++) { f[i] = 1; // 設(shè)f[i]默認(rèn)為1,找不到前面數(shù)字小于自己的時(shí)候就為1 for (int j = 0; j < i; j++) { if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1); // 前一個(gè)小于自己的數(shù)結(jié)尾的最大上升子序列加上自己,即+1 } mx = max(mx, f[i]); } cout << mx << endl; return 0; }
最長上升子序列 II
會(huì)發(fā)現(xiàn)II的數(shù)據(jù)范圍變了,那我們就得做優(yōu)化,怎么優(yōu)化呢?
狀態(tài)表示:f[i]表示長度為i的最長上升子序列,末尾最小的數(shù)字。(長度為i的最長上升子序列所有結(jié)尾中,結(jié)尾最小min的) 即長度為i的子序列末尾最小元素是什么。
狀態(tài)計(jì)算:對(duì)于每一個(gè)w[i], 如果大于f[cnt-1] (下標(biāo)從0開始,cnt長度的最長上升子序列,末尾最小的數(shù)字),那就cnt+1,使得最長上升序列長度+1,當(dāng)前末尾最小元素為w[i]。 若w[i]小于等于f[cnt-1],說明不會(huì)更新當(dāng)前的長度,但之前末尾的最小元素要發(fā)生變化,找到第一個(gè) 大于或等于 (這里不能是大于) w[i],更新以那時(shí)候末尾的最小元素。
f[i]一定以一個(gè)單調(diào)遞增的數(shù)組,所以可以用二分法來找第一個(gè)大于或等于w[i]的數(shù)字。
時(shí)間復(fù)雜度
O(nlogn)狀態(tài)數(shù)(n) * 轉(zhuǎn)移數(shù)(logn)
#include <iostream> using namespace std; const int N = 1010; int n, cnt; int w[N], f[N]; int main() { cin >> n; for (int i = 0 ; i < n; i++) cin >> w[i]; f[cnt++] = w[0]; for (int i = 1; i < n; i++) { if (w[i] > f[cnt-1]) f[cnt++] = w[i]; else { int l = 0, r = cnt - 1; while (l < r) { int mid = (l + r) >> 1; if (f[mid] >= w[i]) r = mid; else l = mid + 1; } f[r] = w[i]; } } cout << cnt << endl; return 0; }
最長公共子序列
集合表示:f[i][j]表示a的前i個(gè)字母,和b的前j個(gè)字母的最長公共子序列長度
集合劃分:以a[i],b[j]是否包含在子序列當(dāng)中為依據(jù),因此可以分成四類:
- ①a[i]不在,b[j]不在
max=f[i−1][j−1]
- ②a[i]a[i]不在,b[j]b[j]在
看似是max=f[i−1][j] , 實(shí)際上無法用f[i−1][j]表示,因?yàn)閒[i−1][j]表示的是在a的前i-1個(gè)字母中出現(xiàn),并且在b的前j個(gè)字母中出現(xiàn),此時(shí)b[j]不一定出現(xiàn),這與條件不完全相等,條件給定是a[i]一定不在子序列中,b[j]一定在子序列當(dāng)中,但仍可以用f[i−1][j]來表示,原因就在于條件給定的情況被包含在f[i−1][j]中,即條件的情況是f[i−1][j]的子集,而求的是max,所以對(duì)結(jié)果不影響。
例如:要求a,b,c的最大值可以這樣求:max(max(a,b),max(b,c))雖然b被重復(fù)使用,但仍能求出max,求max只要保證不漏即可。
- ③a[i],b[j]不在 原理同②
- ④a[i]在,b[j]在 max=f[i−1][j−1]+1
實(shí)際上,在計(jì)算時(shí),①包含在②和③的情況中,所以①不用考慮
#include <iostream> using namespace std; const int N = 1010; int n , m; char a[N] , b[N]; int f[N][N]; int main() { cin >> n >> m; cin >> a + 1 >> b + 1; for(int i = 1 ; i <= n ; i++) for(int j = 1 ; j <= m ; j++) { f[i][j] = max(f[i - 1][j] , f[i][j - 1]);//2和3的情況一定存在,所以可以無條件優(yōu)先判斷 if(a[i] == b[j]) f[i][j] = max(f[i][j] , f[i - 1][j - 1] + 1); } cout << f[n][m] << endl; return 0; }
到此這篇關(guān)于C語言深入探究動(dòng)態(tài)規(guī)劃之線性DP的文章就介紹到這了,更多相關(guān)C語言 線性DP內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入理解goto語句的替代實(shí)現(xiàn)方式分析
本篇文章是對(duì)goto語句的替代實(shí)現(xiàn)方式進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C語言實(shí)現(xiàn)數(shù)獨(dú)游戲的求解
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)數(shù)獨(dú)游戲的求解,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01C++實(shí)現(xiàn)“隱藏實(shí)現(xiàn),開放接口”的方案
本文從一個(gè)實(shí)例講解了C++實(shí)現(xiàn)“隱藏實(shí)現(xiàn),開放接口”的方案,文章條理清新,內(nèi)容充實(shí),需要的朋友可以參考下2015-07-07使用C++實(shí)現(xiàn)Excel文件與CSV之間的相互轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了如何使用C++實(shí)現(xiàn)Excel文件與CSV之間的相互轉(zhuǎn)換,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-06-06