動態規劃4.2——子序列問題
題目上添加了超連結,大家點一下題目就會自動跳轉到Poj原題介面~~ 衝鴨衝鴨ヾ(◍°∇°◍)ノ゙。
前言:建議大家按隨筆順序閱覽,序列型動態規劃是常見動態規劃型別當中的重點, 佔常見動態規劃型別裡的20%,這裡給出的幾道題都很經典,大家可以都當作板子抄下來。
動態規劃組成部分:
1:確定狀態
—確定最後一步(最優策略)
—抽象子問題
2:歸納轉移方程
3:初始條件和邊界情況
4:計算順序
4.2.1 Longest Ordered Subsequence (2533)
題意:在一個給定的數值序列中,找到一個子序列,使得這個子序列元素的數值依次遞增,並且這個子序列的長度儘可能地大,子序列中的元素在原序列中不一定是連續的。
小筆記:最長遞增子序列
#include <cstdio> #include <algorithm> using namespace std; const int N = 1001; int main() { int n; scanf("%d", &n); int ans = 1; int D[N]; for (int i = 0; i < n; i++) { int a[N]; scanf("%d", &a[i]); D[i] = 1; for (int j = 0; j < i; j++) if (a[j] < a[i]) // * 就這兒! D[i] = max(D[i], D[j] + 1); ans = max(ans, D[i]); } printf("%d\n", ans); return 0; }
以上程式碼求的是最長遞增子序列長度,其他情況修改程式碼中*的位置:
①最長不降子序列長度,改成a[j] ≤ a[i];
②最長遞減子序列長度,改成a[j] > a[i];
③最長不升子序列長度,改成a[j] ≥ a[i];
4.2.2 Wooden Sticks (1065)
題意:有n個木棍,已知長度和重量,將木棍放在機器上加工。機器的設定時間
①第一個木棍需要1分鐘;
②處理的木棍長度為l,重量為w,如果下一個木棍長度l’≤l並且w’≤w,不需要設定時間,否則需要1分鐘。
計算處理n個木棍的最少設定時間。
小筆記:最長不上升子序列、偏序、這題也可以貪心解
#include <cstdio> #include <algorithm> using namespace std; const int N = 5001; int main() { int t; scanf("%d", &t); while (t--) { int n; scanf("%d", &n); pair<int, int> a[N]; for (int i = 0; i < n; i++) scanf("%d%d", &a[i].first, &a[i].second); sort(a, a + n); //通過二分查詢方法實現最長遞減子序列的值 int k = 0; int d[N]; for (int i = 0; i < n; i++) { int p = -1; int r = k; while (r - p > 1) { int mid = (p + r) / 2; if (d[mid] > a[i].second) p = mid; else r = mid; } d[r] = a[i].second; //將已經搜尋到的子序列儲存到d中 if (r == k) k++; } printf("%d\n", k); } return 0; }
4.2.3 Alignment (1836)
題意:n個士兵站成一行,佇列中任何士兵的某一側(左側或者右側)沒有身高大於或者等於他的士兵,他就可以看到佇列這一側的最外面的位置。為了使佇列中的每一個士兵都可以看到他某一側的最外面位置,問最少出列多少士兵可以使剩下的士兵滿足這個條件。
小筆記:最長遞增子序列
#include <cstdio> #include <algorithm> using namespace std; const int N = 1005; int main() { double a[N]; int n; scanf("%d", &n); int L[N], R[N]; for (int i = 0; i < n; i++) { scanf("%lf", &a[i]); L[i] = 1; for (int j = 0; j < i; j++) if (a[j] < a[i]) L[i] = max(L[i], L[j] + 1); } for (int i = n - 1; i >= 0; i--) { R[i] = 1; for (int j = n - 1; j > i; j--) if (a[j] < a[i]) R[i] = max(R[i], R[j] + 1); } int ans = 1; for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) ans = max(ans, L[i] + R[j]); printf("%d\n", n - ans); return 0; }
4.2.4 Maximum sum (2479)
題意:n個整數構成的數列a[1…n],求d(A)。
小筆記:最大連續子序列和
#include <cstdio> #include <algorithm> using namespace std; const int N = 50005; const int MIN = -10000; int main() { int t; scanf("%d", &t); while (t--) { int n; scanf("%d", &n); int L[N], R[N]; L[0] = R[n + 1] = MIN; int a[N]; for (int i = 0; i < n; i++) { scanf("%d", &a[i]); L[i + 1] = max(a[i], L[i] + a[i]); } for (int i = 1; i <= n; i++) L[i] = max(L[i - 1], L[i]); for (int i = n; i > 0; i--) R[i] = max(a[i - 1], R[i + 1] + a[i - 1]); for (int i = n; i > 0; i--) R[i] = max(R[i + 1], R[i]); int ans = MIN; for (int i = 1; i < n; i++) ans = max(ans, L[i] + R[i + 1]); printf("%d\n", ans); } return 0; }
4.2.5To the Max (1050)
題意:一個二維矩陣包含正數和負數,任何連續的矩陣空間構成它的子矩陣,矩陣中所有元素的和稱為矩陣的和,求最大的子矩陣和。
小筆記:進階,最大子矩陣和。按照合適的順序將求子矩陣的和的計算轉化成求子序列的和,在求的過程中記錄最大值。
題解: ①從左到右計算每一行,將Σa[i,1…j]儲存到a[i,j]中,這樣,任意一段Σa[i,k…j]就可以通過a[i,j]-a[i,k]得到;
②先取1列,計算由1列組成的最大子矩陣和,因為只有1列,相當於求最大的連續子序列和。這裡與上一個問題求最大連續子序列和不同,本題的連續子序列必須包含i點,而上題不需要。
③擴充套件到多列的情況,因為k…j列可以由a[i,j]-a[i,k]得出,依舊是1個值,計算方法和②一樣;
④最後擴充套件到所有列,就找到了最大子矩陣和。
#include <cstdio> #include <algorithm> using namespace std; int main() { int ans = -128; int a[101][101] = {0}; int n; scanf("%d", &n); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { int t; scanf("%d", &t); a[i][j] = a[i][j - 1] + t; } for (int j = 1; j <= n; j++) for (int k = 0; k < j; k++) for (int i = 1, sum = 0; i <= n; i++) { sum = a[i][j] - a[i][k] + max(0, sum); ans = max(ans, sum); } printf("%d\n", ans); return 0; }