動態規劃_區間DP
阿新 • • 發佈:2020-07-21
282. 石子合併
- 狀態表示:合併\(l\)到\(r\)這一區間需要的最小代價
- 狀態計算:
- 列舉區間長度
- 列舉分割點,將所有狀態進行劃分
#include <iostream> using namespace std; const int N = 310; int a[N], s[N], dp[N][N]; int main() { int n; cin >> n; for(int i = 1; i <= n; i++) { cin >> a[i]; s[i] = s[i - 1] + a[i]; } // 列舉區間長度 for(int len = 2; len <= n; len++) { // 確定左右端點 for(int i = 1; i + len - 1 <= n; i++) { int l = i, r = i + len - 1; dp[l][r] = 1e9; // 列舉決策點 for(int k = l; k < r; k++) dp[l][r] = min ( dp[l][r], dp[l][k] + dp[k + 1][r] + s[r] - s[l - 1] ); } } cout << dp[1][n]; return 0; }
環形DP
1068. 環形石子合併
- 利用倍增,將環轉換成線性的
#include <iostream> using namespace std; const int N = 210, M = N << 1; int a[N], s[M], h[M]; int dp[N][N], f[N][N]; int main() { int n; cin >> n; for(int i = 0; i < n; i++) cin >> a[i]; for(int i = 0; i < 2 * n; i++) s[i] = a[i % n] + s[i - 1]; // for(int i = 0; i < 2 * n; i++) cout << s[i] << " "; // cout << endl; for(int g = 0; g < n; g++) { for(int len = 2; len <= n; len++) { for(int i = g; i + len - 1 < 2 * n; i++) { int l = i, r = i + len - 1; dp[l][r] = 1e9, f[l][r] = -2e9; for(int k = l; k < r; k++) { dp[l][r] = min( dp[l][r], dp[l][k] + dp[k + 1][r] + s[r] - s[l - 1] ); f[l][r] = max( f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[i - 1] ); // cout << l << " " << r << " " << dp[l][r] << " " << endl;; } } } } int mi = 1e9, mx = -1; for(int i = 0; i < n; i++) { mi = min(mi, dp[i][i + n - 1]); mx = max(mx, f[i][i + n - 1]); } cout << mi << endl << mx; return 0; }
1069. 凸多邊形的劃分
- 區間DP + 高精度演算法
- 狀態表示:從頂點\(i\)到\(j\)之間劃分成\(N - 2\)個互不相交的三角形的集合
- 狀態計算: \(dp[i][j] = min(dp[i][j], dp[i][k] * dp[k][j] + w[i] * w[k] * w[j]\)
#include <iostream> #include <vector> #include <algorithm> using namespace std; typedef long long LL; const int N = 55, M = 100, INF = 1e9; int n; LL w[N]; string dp[M][M]; string add(string a, string b) { if(a.size() < b.size()) return add(b, a); vector<LL> x, y; string ans = ""; for(LL i = a.length() - 1; i >= 0; i--) x.push_back(a[i] - '0'); for(LL i = b.length() - 1; i >= 0; i--) y.push_back(b[i] - '0'); LL t = 0; for(LL i = 0; i < (int)x.size(); i++) { t += x[i]; if(i < (int)y.size()) t += y[i]; ans += ((t % 10) + '0'); t /= 10; } if(t) ans += "1"; reverse(ans.begin(), ans.end()); return ans; } string mul(string a, LL b) { vector<LL> x; string ans = ""; for(LL i = a.length() - 1; i >= 0; i--) x.push_back(a[i] - '0'); LL t = 0; for(LL i = 0; i < (int)x.size() || t; i++) { if(i < (int)x.size()) t += x[i] * b; ans += ((t % 10) + '0'); t /= 10; } while(ans.back() == '0' && ans.size() > 1) ans.pop_back(); reverse(ans.begin(), ans.end()); return ans; } string get_min(string x, string y) { if (x.size() == 0) return y; if (y.size() == 0) return x; if (x.size() > y.size()) return y; else if (x.size() < y.size()) return x; return x < y ? x : y; } int main() { cin >> n; for(LL i = 1; i <= n; i++) cin >> w[i]; for(LL len = 3; len <= n; len ++) for(LL l = 1; l + len - 1 <= n; l++) { LL r = l + len - 1; dp[l][r] = "10000000000000000000000000000000"; for(LL k = l + 1; k < r; k++) { string tmp = "1"; tmp = mul(mul(mul(tmp, w[l]), w[k]), w[r]); tmp = add(add(tmp, dp[l][k]), dp[k][r]); dp[l][r] = get_min(dp[l][r], tmp); } } cout << dp[1][n]; return 0; }
區間DP記錄方案
479. 加分二叉樹
- 狀態表示:中序遍歷為i~j的二叉樹的得分的最大值
- 以根節點的位置作為集合劃分的依據
- 同時記錄每顆子樹的根
#include <iostream>
#include <cstring>
using namespace std;
const int N = 30;
int w[N], dp[N][N], g[N][N];
void dfs(int x, int y) {
if(x > y) return ;
int k = g[x][y];
cout << k << " ";
dfs(x, k - 1);
dfs(k + 1, y);
}
int main() {
int n; cin >> n;
for(int i = 1; i <= n; i++) cin >> w[i];
for(int len = 1; len <= n; len ++) {
for(int l = 1; len + l - 1 <= n; l++) {
int r = l + len - 1;
// cout << len << " " << l << " " << r << endl;
if(len == 1) {
dp[l][r] = w[l];
g[l][r] = l;
} else {
// 列舉根的位置
for(int k = l; k <= r; k++) {
// k == l, 說明左子樹為空
int left = (k == l) ? 1 : dp[l][k - 1];
// 同理,k == r, 說明右子樹為空
int right = (k == r) ? 1 : dp[k + 1][r];
if(dp[l][r] < left * right + w[k]) {
dp[l][r] = left * right + w[k];
// 記錄當前子樹的根節點
g[l][r] = k;
}
}
}
}
}
cout << dp[1][n] << endl;
dfs(1, n);
return 0;
}