张嘉锡V

导航

 

282. 石子合并

  • 状态表示:合并\(l\)\(r\)这一区间需要的最小代价
  • 状态计算:
    1. 枚举区间长度
    2. 枚举分割点,将所有状态进行划分
#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;
}
posted on 2020-07-20 22:03  张嘉锡V  阅读(154)  评论(0编辑  收藏  举报