acwing算法提高之动态规划--区间DP

目录

  • 1 基础知识
  • 2 模板
  • 3 工程化

1 基础知识

暂无。。。

2 模板

暂无。。。

3 工程化

题目1:环形石子合并。

解题思路:已知石子合并的求解方式,关键是如何化解环形。可以将两个相同数组拼起来,答案就是f[1][n], f[2][n+1], f[3][n+2], ..., f[n][2*n-1]中的最小值。

区间DP的状态的遍历模板为,

for (int len = 1; len <= n; ++len) {
	for (int l = 1; l + len - 1 <= 2 * n; ++l) {
		int r = l + len - 1;
		//进行状态计算操作...
	}
}

此处,状态定义f[i][j]:表示合并[i,j]石子的最小代价。

状态转移为:

f[i][j];
for (int k = i; k < j; ++k) {
	f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
}

其中s[]表示前缀和数组。

C++代码如下,

#include 
#include 

using namespace std;

const int N = 210;
int n;
int w[N * 2];
int s[N * 2];
int f[N * 2][N * 2]; //最小代价
int g[N * 2][N * 2]; //最大代价

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> w[i];
    for (int i = n + 1; i <= 2 * n; ++i) w[i] = w[i - n];
    
    for (int i = 1; i <= 2 * n; ++i) s[i] = s[i - 1] + w[i];
    
    memset(f, 0x3f, sizeof f); //初始化
    memset(g, -0x3f, sizeof g); //初始化
    
    //状态计算
    for (int len = 1; len <= n; ++len) {
        for (int l = 1; l + len - 1 <= 2 * n; ++l) {
            int r = l + len - 1;
            if (l == r) f[l][r] = g[l][r] = 0;
            else {
                for (int k = l; k < r; ++k) {
                    f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
                    g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
                }
            }
        }
    }
    
    int maxv = -0x3f3f3f3f, minv = 0x3f3f3f3f;
    for (int i = 1; i <= n; ++i) {
        minv = min(minv, f[i][n + i - 1]);
        maxv = max(maxv, g[i][n + i - 1]);
    }
    
    cout << minv << endl << maxv << endl;
    
    return 0;
}

题目2:能量项链。

解题思路:主要就是把题目的意思理解好,解题方法同题目1。

C++代码如下,

#include 

using namespace std;

const int N = 210;

int n;
int w[N];
int f[N][N];

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> w[i];
        w[i + n] = w[i];
    }
    
    //计算状态
    for (int len = 3; len <= n + 1; ++len) {
        for (int l = 1; l + len - 1 <= 2 * n; ++l) {
            int r = l + len - 1;
            for (int k = l + 1; k < r; ++k) {
                f[l][r] = max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]);
            }
        }
    }
    
    int res = 0;
    for (int i = 1; i <= n; ++i) res = max(res, f[i][n + i]);
    cout << res << endl;
    
    return 0;
}

题目3:凸多边形的划分。

解题思路:区间DP,注意状态定义和转移时的细节。需要用到高精度计算。

C++代码如下(这里就不写高精度版本了,写个普通版本,高精度版本以后再补充),

#include 

using namespace std;

const int N = 60;
int n;
int w[N];
int f[N][N];

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> w[i];
    
    for (int len = 3; len <= n; ++len) {
        for (int l = 1; l + len - 1 <= n; ++l) {
            int r = l + len - 1;
            f[l][r] = 0x3f3f3f3f;
            for (int k = l + 1; k < r; ++k) {
                f[l][r] = min(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]);
            }
        }
    }
    
    cout << f[1][n] << endl;
    
    return 0;
}

题目4:加分二叉树。

解题思路:区间DP,关键此题需要记录最佳价值对应的具体二叉树。咋做呢?记录每个区间的最佳方案的根结点就行了。

状态定义f[l][r]:所有中序遍历是[l,r]这一段的二叉树的集合。属性是最大值。

状态转移,以下情况的最大值,

f[l][r] = max(f[l][r], f[l][k - 1] + f[k + 1][r] + w[k])

C++代码如下,

#include 

using namespace std;

const int N = 30;

int n;
int w[N];
int f[N][N], g[N][N];

void dfs(int l, int r) {
    if (l > r) return;
    
    int root = g[l][r];
    cout << root << " ";
    
    dfs(l, root - 1);
    dfs(root + 1, r);
    return;
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> w[i];
    
    for (int len = 1; len <= n; ++len) {
        for (int l = 1; l + len - 1 <= n; ++l) {
            int r = l + len - 1;
            if (l == r) {
                f[l][r] = w[l];
                g[l][r] = l;
            } else {
                for (int k = l; k <= r; ++k) {
                    int a = k == l ? 1 : f[l][k - 1];
                    int b = k == r ? 1 : f[k + 1][r];
                    int score = a * b + w[k];
                    if (f[l][r] < score) {
                        f[l][r] = score;
                        g[l][r] = k;
                    }
                }
            }
        }
    }
    
    cout << f[1][n] << endl;
    
    dfs(1, n);
    
    return 0;
}

题目5:棋盘分割。

解题思路:区间DP,状态转移那部分没有搞懂,后续再说

C++代码如下,

#include 
#include 
#include 
#include 

using namespace std;

const int N = 15, M = 9;
const double INF = 1e9;

int n, m = 8;
int s[M][M];
double f[M][M][M][M][N];
double X;

int get_sum(int x1, int y1, int x2, int y2) {
    return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
}

double get(int x1, int y1, int x2, int y2) {
    double sum = get_sum(x1, y1, x2, y2) - X;
    return sum * sum / n;
}

double dp(int x1, int y1, int x2, int y2, int k) {
    double &v = f[x1][y1][x2][y2][k];
    if (v >= 0) return v;
    if (k == 1) return v = get(x1, y1, x2, y2);
    
    v = INF;
    for (int i = x1; i < x2; ++i) {
        v = min(v, get(x1, y1, i, y2) + dp(i + 1, y1, x2, y2, k - 1));
        v = min(v, get(i + 1, y1, x2, y2) + dp(x1, y1, i, y2, k - 1));
    }
    
    for (int i = y1; i < y2; ++i) {
        v = min(v, get(x1, y1, x2, i) + dp(x1, i + 1, x2, y2, k - 1));
        v = min(v, get(x1, i + 1, x2, y2) + dp(x1, y1, x2, i, k - 1));
    }
    
    return v;
}

int main() {
    cin >> n;
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= m; ++j) {
            cin >> s[i][j];
            s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1];
        }
    }
    
    X = (double)s[m][m] / n;
    memset(f, -1, sizeof f);
    printf("%.3lf\n", sqrt(dp(1, 1, 8, 8, n)));
    
    return 0;
}

你可能感兴趣的:(Acwing,C++学习,算法,动态规划)