区间DP
AcWing 1068. 环形石子合并【区间DP+环形区间问题】
最经典的区间dp问题,先枚举len再枚举左右端点,这样的原因是可以吧每个状态都表示出来不漏掉每一个状态
#include <iostream>
#include <cstring>
const int N = 400 + 10, INF = 0x3f3f3f3f;//因为是环形我们把它拓展成一个链了
int g[N][N], f[N][N];
int w[N], s[N];
int n;
void solve()
{
//input
std::cin >> n;
for(int i = 1; i <= n; ++ i) std::cin >> w[i], w[i + n] = w[i];
//预处理前缀和,快速获得一个区间内的石子被合并的得分
//从这里我们可以发现,我们处理的前缀和是这样的:s[r] - s[l] = w[l + 1] + w[l + 2] + ... + w[r]
for (int i = 1; i <= n << 1; ++ i) s[i] = s[i - 1] + w[i];
//dp,
//dp初始化
memset(f, 0x3f, sizeof f);
memset(g, - 0x3f, sizeof g);
for (int len = 1; len <= n; ++ len)
//for (int l = 1, r = l + len - 1; r <= n << 1; ++ l) 会segemention fault
for (int l = 1, r; r = l + len - 1, r <= n << 1; ++ l)
{
if (len == 1) f[l][l] = g[l][l] = 0;//如果长度为1就用来初始化
else
{
//如果长度不为1
for (int k = l; k + 1 <= r; ++ k)
{
f[l][r] = std::min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
g[l][r] = std::max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
}
}
}
int minV = INF, maxV = -INF;
for (int l = 1; l <= n; ++ l)
{
minV = std::min(minV, f[l][l + n - 1]);
maxV = std::max(maxV, g[l][l + n - 1]);
}
std::cout << minV << std::endl << maxV;
return ;
}
int main()
{
solve();
return 0;
}
AcWing 320. 能量项链【区间DP】
本题代码模版就是区间dp的代码模版但是
这一段我说实话没看懂
#include <iostream>
#include <cstring>
const int N = 100, M = (N << 1) + 10, INF = 0x3f3f3f3f;
int w[M], f[M][M];
int n;
void solve()
{
//input
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> w[i], w[i + n] = w[i];
//dp
memset(f, -0x3f, sizeof f);
for (int len = 2; len <= n + 1; ++ len)
for (int l = 1, r; (r = l + len - 1) <= n << 1; ++ l)
if (len == 2) f[l][r] = 0;//本题可以不用初始化,因为不存在负数的状态值,因此0就是最小的
else
{
for (int k = l + 1; k + 1 <= r; ++ k)
{
f[l][r] = std::max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]);// f[l][k] + f[k][r] + w[l] * w[k] * w[r]可以看出为什么我们我们要遍历n + 1
}
}
int ans = 0;
for (int l = 1; l <= n; ++ l)
{
ans = std::max(ans, f[l][l + n]);
}
std::cout << ans;
return ;
}
int main()
{
solve();
return 0;
}
AcWing 479. 加分二叉树[记忆化搜索]
这题我们应该学会的是如何在dp决策(赋值)过程中记录我们的方案。
同时这题也可以用记忆化搜索的思想过掉,但是我赶进度就pass了,因为我还不知道啥是记忆化搜索
#include <iostream>
const int N = 31;
int f[N][N], g[N][N];
int w[N];
int n;
//这个前序遍历的输出就是要想像一下当前(l,r)是一个经过中序遍历过的序列,
//我们在dp的过程中用g[l][r]记录我们决策f[l][r]时 l - r区间的根结点,
//那么整棵树是以g[l][r]为根结点的,由中序遍历的特点,左边的树的区间就是 (l, g[l][r] - 1),右边的树的区间就是(g[l][r] + 1, r)
void dfs(int l, int r)
{
if (l > r) return ;
else
{
std::cout << g[l][r] << " ";
dfs(l, g[l][r] - 1);
dfs(g[l][r] + 1, r);
}
}
void solve()
{
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> w[i];
for (int len = 1; len <= n; ++ len)
{
for (int l = 1, r; l + len - 1 <= n; ++ l)
{
r = l + len - 1;
if (len == 1) f[l][r] = w[l], g[l][r] = l;
else
{
for (int k = l; k < r; ++ k)
{
// left表示左子树区间的值
int left = k == l ? 1 : f[l][k - 1];//根节点为k的时候如果根节点和枚举的这棵树的左区间左端点重合了,说明左子树为空
int right = k == r ? 1 : f[k + 1][r];
int score = left * right + w[k];
if (f[l][r] < score)
{
f[l][r] = score;
g[l][r] = k;
}
}
}
}
}
std::cout << f[1][n] << std::endl;
dfs(1, n);
return ;
}
int main()
{
solve();
return 0;
}
AcWing 1069. 凸多边形的划分【区间dp + 高精度】
#include <iostream>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 55;
int w[N];
vector<int> f[N][N];
int n;
bool cmp(vector<int> a, vector<int> b)
{
if (a.size() != b.size()) return a.size() < b.size();
else
{
for (int i = a.size() - 1; i >= 0; -- i)//下面说了,这里的高精度模版全是反的,因此比较的时候也反着比
{
if (a[i] != b[i]) return a[i] < b[i];
}
}
return true;
}
vector<int> add(vector<int> a, vector<int> b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size() || i < b.size(); ++ i)
{
if (i < a.size()) t += a[i];
if (i < b.size()) t += b[i];
c.push_back(t % 10);
t /= 10;
}
while (t) c.push_back(t % 10), t /= 10;
return c;
}
vector<int> mul(vector<int> a, LL b)
{
vector<int> c;
LL t = 0;
for (int i = 0; i < a.size(); ++ i)
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t) c.push_back(t % 10), t /= 10;
return c;
}
void solve()
{
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> w[i];
//dp;
for (int len = 3; len <= n ; ++ len)
{
for (int l = 1, r; (r = l + len - 1) <= n; ++ l)
{
for (int k = l + 1; k < r; ++ k)//k从l + 1开始,因为l,k,r是三角形的三个顶点,肯定不能重合
{
auto newVal = mul(mul({
w[l]}, w[k]), w[r]);
newVal = add(add(newVal, f[l][k]), f[k][r]);
if (f[l][r].empty() || cmp(newVal, f[l][r])) f[l][r] = newVal;
}
}
}
auto res = f[1][n];
for (int i = res.size() - 1; i >= 0; --i) std::cout << res[i];//本题的高精度的模版全是反的,我就反着算,输出的时候也反转,负负得正
return ;
}
int main()
{
solve();
return 0;
}
AcWing 321. 棋盘分割【记忆化搜索/二维区间DP】
这题遍历的时候下标从1开始,数组大小要开到9,不能是8,之前开8一直se,不过我好像知道了什么是记忆化搜索了,应该是剪枝的一种。
#include <iostream>
#include <cstring>
#include <cmath>
const int N = 16;
double f[N][9][9][9][9];
double X;//平均数
int s[9][9], n;
double get(int x1, int y1, int x2, int y2)
{
double delta = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
delta -= X;
return delta * delta;
}
double dp(int k, int x1, int y1, int x2, int y2)
{
if (f[k][x1][y1][x2][y2] >= 0) return f[k][x1][y1][x2][y2]; //记忆化搜索
if (k == n) return f[k][x1][y1][x2][y2] = get(x1, y1, x2, y2); //更新初始状态
double t = 1e9; //初始化为无穷大
for (int i = x1; i < x2; i ++ ) //横着切
{
t = std::min(t, dp(k + 1, x1, y1, i, y2) + get(i + 1, y1, x2, y2));
t = std::min(t, dp(k + 1, i + 1, y1, x2, y2) + get(x1, y1, i, y2));
}
for (int i = y1; i < y2; i ++ ) //竖着切
{
t = std::min(t, dp(k + 1, x1, y1, x2, i) + get(x1, i + 1, x2, y2));
t = std::min(t, dp(k + 1, x1, i + 1, x2, y2) + get(x1, y1, x2, i));
}
return f[k][x1][y1][x2][y2] = t;
}
void solve()
{
//input
std::cin >> n;
for (int i = 1; i <= 8; ++ i)
{
for (int j = 1; j <= 8; ++ j)
std::cin >> s[i][j];
}
//预处理
for (int i = 1; i <= 8; ++ i)
{
for (int j = 1; j <= 8; ++ j)
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
X = (double)(s[8][8]) / n;
//dp
memset(f, -1, sizeof f);
double res = dp(1, 1, 1, 8, 8);
//output
printf("%.3lf", sqrt(res / n));
return ;
}
int main()
{
solve();
return 0;
}