算法讲解:Here

AcWing 282. 石子合并 (模板)

题目链接:Here

const int N = 310;
int a[N], s[N];
int dp[N][N];
void solve() {
int n; cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
s[i] += s[i - 1] + a[i];
}
// 区间 DP 枚举套路:长度+左端点
for (int len = 1; len < n; ++len) {// len表示i和j堆下标的差值
for (int i = 1; i + len <= n; i ++) {
int j = i + len; // 自动得到右端点
dp[i][j] = 1e8;
for (int k = i; k <= j - 1; k ++) { // 必须满足k + 1 <= j
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + s[j] - s[i - 1]);
}
}
}
cout << dp[1][n] << "\n";
}

「NOIP2006」能量项链 (环形石子合并)

题目链接:Here

题面

在Mars星球上,每个Mars人都随身佩带着一串能量项链。在项链上有N颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是Mars人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量珠的头标记为r,尾标记为n,则聚合后释放的能量为 $m*r*n$(Mars单位),新产生的珠子的头标记为m,尾标记为n。需要时,Mars人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。

思路1:

(2,3) (3,5) (5,10) (10,2)

换种表达方式

2 3 5 10 2

所以实际还要往后面乘一个数,这样缺点?

这样表达方式也要学习。一开始自己的想法居然是利用pair, 然后预处理,看来还是要多观察数据的特性

\(f[l,r]\) 所有将 \([l,r]\) 合并的方式的Max

这种划分方式,中间是公用的!

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

特殊情况解释:

  1. 如何只有一个矩阵,代价为0,len从3开始枚举没问题
  2. 最后一次合并相当于\([l,k],[k,r]\)

把合并n颗珠子的问题转化为合并 \((n+1)\) 个数合并的问题,只不过有一个数是公用的,注意不要间断分割

区间长度为:\(n + 1\)

const int N = 210;
int a[N], f[N][N];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n; cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[i + n] = a[i];
}
//长度为3实际上只是包含了2颗珠子的首尾信息
for (int len = 3; len <= n + 1; ++len)
for (int i = 1; i + len - 1 <= 2 * n; ++i) {
int j = len + i - 1;
for (int k = i + 1; k < j; ++k) // 最长的串,只计算一边的内容,所以l+1
f[i][j] = max(f[i][j], f[i][k] + f[k][j] + a[i] * a[k] * a[j]);
//这里不能间断分割,因为有一个公用的量,同石子合并不同
}
int ans = INT_MIN;
for (int i = 1; i <= n; ++i) ans = max(ans, f[i][i + n]);
// 注意这里实际上默认的是长度公式为n+1
cout << ans << '\n';
}

思路2:直接当成珠子来看待即可,模拟合并过程。区间长度为:\(n\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long; const int N = 210;
int a[N], f[N][N];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n; cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[i + n] = a[i];
}
for (int len = 1; len <= n; ++len)
for (int i = 1; i + len - 1 <= n * 2; ++i) {
int j = len + i - 1;
if (len == 1) f[i][j] = 0;
//这里的过程同石子合并,这里不难想到若将l到k的珠子合并之后会变成一个首是l而尾k+1的珠子;
//同理若将k+1到r的珠子合并之后会变成一个首是k+1而尾r+1的珠子;
for (int k = i; k < j; ++k)
f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j] + a[i] * a[k + 1] * a[j + 1]);
}
int ans = INT_MIN;
for (int i = 1; i <= n; ++i) ans = max(ans, f[i][i + n - 1]);
cout << ans << '\n';
}

AcWing 283. 多边形

题目链接:Here

这是一个标准的区间DP问题,这个题跟石子合并非常相似,只不过它是一个环形结构,所以形成一个2倍长度的链就可以很好的解决环形区间DP问题。

代码只是照着算法竞赛进阶指南的思路和之前总结的模板手撸了一遍,而且写的好像有点蠢就不放了

AcWing 284. 金字塔 (区间dp的递归算法和记忆化搜索)

题目链接:Here

const int N = 310, mod = 1e9;
char str[N];
int n;
int f[N][N];
int dp(int l, int r) {
if (l > r) return 0;
if (l == r) return 1;
int& ans = f[l][r];
if (ans != -1) return ans;
ans = 0;
if (str[l] == str[r])
for (int k = l + 2; k <= r; k++)
ans = (ans + (ll)dp(l + 1, k - 1) * (ll)dp(k, r)) % mod;
return ans;
}
void solve() {
cin >> str + 1;
n = strlen(str + 1);
memset(f, -1, sizeof(f));
cout << dp(1, n) << "\n";
}

AcWing 321. 棋盘分割

题目链接:Here

using namespace std;
typedef long long llong;
typedef set<int>::iterator ssii; #define Cmp(a, b) memcmp(a, b, sizeof(b))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define Set(a, v) memset(a, v, sizeof(a))
#define debug(x) cout << #x << ": " << x << endl
#define _forS(i, l, r) for(set<int>::iterator i = (l); i != (r); i++)
#define _rep(i, l, r) for(int i = (l); i <= (r); i++)
#define _for(i, l, r) for(int i = (l); i < (r); i++)
#define _forDown(i, l, r) for(int i = (l); i >= r; i--)
#define debug_(ch, i) printf(#ch"[%d]: %d\n", i, ch[i])
#define debug_m(mp, p) printf(#mp"[%d]: %d\n", p->first, p->second)
#define debugS(str) cout << "dbg: " << str << endl;
#define debugArr(arr, x, y) _for(i, 0, x) { _for(j, 0, y) printf("%c", arr[i][j]); printf("\n"); }
#define _forPlus(i, l, d, r) for(int i = (l); i + d < (r); i++)
#define lowbit(i) (i & (-i)) const int N = 8;
const int maxn = 15 + 2; int f[maxn][N + 2][N + 2][N + 2][N + 2];
int A[N + 2][N + 2];
int n;
int S[N + 2][N + 2];
double tot = 0;
const int inf = 0x3f3f3f3f; void init() {
Set(f, inf);
Set(S, 0); _rep(i, 1, N) _rep(j, 1, N) {
S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + A[i][j];
} _rep(x1, 1, N) _rep(y1, 1, N) {
_rep(x2, x1, N) _rep(y2, y1, N) {
int t = S[x2][y2] - S[x2][y1-1] - S[x1-1][y2] + S[x1-1][y1-1];
f[0][x1][y1][x2][y2] = t * t;
}
}
} void dp(int k, int x1, int y1, int x2, int y2) {
int& ans = f[k][x1][y1][x2][y2];
ans = inf; _for(a, x1, x2) {
ans = min(ans, f[k-1][x1][y1][a][y2] + f[0][a+1][y1][x2][y2]);
ans = min(ans, f[k-1][a+1][y1][x2][y2] + f[0][x1][y1][a][y2]);
} _for(b, y1, y2) {
ans = min(ans, f[k-1][x1][y1][x2][b] + f[0][x1][b+1][x2][y2]);
ans = min(ans, f[k-1][x1][b+1][x2][y2] + f[0][x1][y1][x2][b]);
}
} int main() {
freopen("input.txt", "r", stdin);
scanf("%d", &n); _rep(i, 1, N) _rep(j, 1, N) {
scanf("%d", &A[i][j]);
tot += A[i][j];
} init();
_rep(k, 1, n - 1) _rep(x1, 1, N) _rep(y1, 1, N) {
_rep(x2, x1, N) _rep(y2, y1, N) {
dp(k, x1, y1, x2, y2);
}
} double avr = (double)(tot / n);
//debug(tot);
double ans = sqrt(1.0 * f[n - 1][1][1][N][N] / n - (avr * avr));
printf("%.3f\n", ans);
}

另外一种思路:

完成划分后,一共会得到 \(n\) 块棋盘。

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。

现在需要把棋盘按上述规则分割成 \(n\) 块矩形棋盘,并使各矩形棋盘总分的均方差最小。

显然可以观察到一个二维 区间 dp 的模型 (不对,二维是不是应该叫面积DP(滑稽))


根据题意,我们可以得到的信息是

1)一共可以对棋盘进行 \(n−1\) 次划分得到 \(n\) 个子棋盘

2)对所有的子棋盘可以求得的平均数 \(\bar x=\frac{∑\limits^n_{i=1}x^i}n\)

3)要求总分的均方差最小 \(σ= \sqrt{\frac{\sum\limits_{i=1}^n(x_i-\bar x)^2}n}\)

/*
1.1) 横着切:
1.1.1)以(x1, y1)为左上角,以(x1, y2)为右下角 或 以(x1+1, y1)为左上角,以(x2, y2)为右下角
1.1.2)以(x1, y1)为左上角,以(x1+1, y2)为右下角 或 以(x1+2, y1)为左上角,以(x2, y2)为右下角
......
1.1.i)以(x1, y1)为左上角,以(x1+i, y2)为右下角 或 以(x1+i+1, y1)为左上角,以(x2, y2)为右下角
......
1.1.x2-1)以(x1, y1)为左上角,以(x2-1, y2)为右下角 或 以(x2, y1)为左上角,以(x2, y2)为右下角 1.2) 竖着切:
1.2.1)以(x1,y1)为左上角,以(x2, y1)为右下角 或 以(x1, y1)为左上角,以(x2, y2)为右下角
1.2.2)以(x1,y1)为左上角,以(x2, y1+1)为右下角 或 以(x1, y1+2)为左上角,以(x2, y2)为右下角
......
1.2.i)以(x1,y1)为左上角,以(x2, y1+i)为右下角 或 以(x1, y1+i+1)为左上角,以(x2, y2)为右下角
......
1.2.y2-1)以(x1, y1)为左上角,以(x2, y2-1)为右下角 或 以(x1,y2-1)为左上角,以(x2, y2)为右下角
*/

2)状态转移方程

模拟上述集合的划分枚举所有的区间即可

由于dp的方程维数过大,写 \(5\) 重迭代太麻烦了,这题采用记忆化搜索

const int N = 9, M = 15;
const double INF = 1e9; int n, m = 8;
int s[N][N];
double f[N][N][N][N][M];
double X; //均值x拔 double get(int x1, int y1, int x2, int y2) {
//根号下,求和符号内的部分
double sum = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1] - X;
//还要平方
return (double)sum * sum;
}
double dp(int x1, int y1, int x2, int y2, int k) {
auto &t = f[x1][y1][x2][y2][k];
if (t >= 0) return t;
if (k == 1) return get(x1, y1, x2, y2); t = INF; //求最小值要初始化成最大值
//横着切
for (int i = x1; i < x2; ++i) {
t = min(t, dp(x1, y1, i, y2, k - 1) + get(i + 1, y1, x2, y2));
t = min(t, dp(i + 1, y1, x2, y2, k - 1) + get(x1, y1, i, y2));
}
//竖着切
for (int i = y1; i < y2; ++i) {
t = min(t, dp(x1, y1, x2, i, k - 1) + get(x1, i + 1, x2, y2));
t = min(t, dp(x1, i + 1, x2, y2, k - 1) + get(x1, y1, x2, i));
}
return t;
} 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;
//记忆化搜索,初始化成NaN
memset(f, -1, sizeof f); //这个就是\sigma的完整计算公式
printf("%.3lf\n", sqrt(dp(1, 1, m, m, n) / n));
return 0;
}

AcWing 322. 消木块 (困难)

题目链接:Here

#define _rep(i, l, r) for(int i = (l); i <= (r); i++)
#define _for(i, l, r) for(int i = (l); i < (r); i++)
const int maxn = 200 + 10;
int f[maxn][maxn][maxn];
int A[maxn];
int N; void init() { Set(f, -1);}
int dp(int i, int j, int k) {
if(i > j) return 0;
int& ans = f[i][j][k];
if(i == j) return ans = (1 + k) * (1 + k);
if(ans >= 0) return ans; int p = j;
while (p >= i && A[p] == A[j]) p--;
p++; ans = dp(i, p-1, 0) + (k + j-p+1) * (k + j-p+1);
_for(q, i, p) {
if(A[q] == A[j] && A[q+1] != A[q]) {
ans = max(ans, dp(i, q, k+j-p+1) + dp(q+1, p-1, 0));
}
} return ans;
} int main() {
int T; scanf("%d", &T);
_rep(kase, 1, T) {
printf("Case %d: ", kase);
scanf("%d", &N);
_rep(i, 1, N) scanf("%d", &A[i]);
init(); // then dp()
printf("%d\n", dp(1, N, 0));
}
}

区间DP练习题题解的更多相关文章

  1. POJ 1191 棋盘分割(区间DP)题解

    题意:中文题面 思路:不知道直接暴力枚举所有情况行不行... 我们可以把答案转化为 所以答案就是求xi2的最小值,那么我们可以直接用区间DP来写.设dp[x1][y1][x2][y2][k]为x1 y ...

  2. HDU 4632 Palindrome subsequence & FJUT3681 回文子序列种类数(回文子序列个数/回文子序列种数 容斥 + 区间DP)题解

    题意1:问你一个串有几个不连续子序列(相同字母不同位置视为两个) 题意2:问你一个串有几种不连续子序列(相同字母不同位置视为一个,空串视为一个子序列) 思路1:由容斥可知当两个边界字母相同时 dp[i ...

  3. HDU 3506 Monkey Party(区间DP)题解

    题意:有n个石堆排成环,每次能合并相邻的两堆石头变成新石堆,代价为新石堆石子数,问最少的总代价是多少 思路:先看没排成环之前怎么做:用dp[i][j]表示合并i到j所需的最小代价,那么dp[i][j] ...

  4. ZOJ 1602 Multiplication Puzzle(区间DP)题解

    题意:n个数字的串,每取出一个数字的代价为该数字和左右的乘积(1.n不能取),问最小代价 思路:dp[i][j]表示把i~j取到只剩 i.j 的最小代价. 代码: #include<set> ...

  5. POJ 2955 Brackets(区间DP)题解

    题意:问最多有几个括号匹配 思路:用dp[i][j]表示i到j最多匹配,若i和j构成匹配,那么dp[i][j] = dp[i + 1][j - 1] + 2,剩下情况dp[i][j] = max(dp ...

  6. HihoCoder 1636 Pangu and Stones(区间DP)题解

    题意:合并石子,每次只能合并l~r堆成1堆,代价是新石堆石子个数,问最后能不能合成1堆,不能输出0,能输出最小代价 思路:dp[l][r][t]表示把l到r的石堆合并成t需要的最小代价. 当t == ...

  7. 【题解】【THUSC 2016】成绩单 LOJ 2292 区间dp

    Prelude 快THUWC了,所以补一下以前的题. 真的是一道神题啊,网上的题解没几篇,而且还都看不懂,我做了一天才做出来. 传送到LOJ:(>人<:) Solution 直接切入正题. ...

  8. luogu1005矩阵取数游戏题解--区间DP

    题目链接 https://www.luogu.org/problemnew/show/P1005 分析 忽然发现这篇题解好像并没有什么意义...因为跟奶牛零食那道题一模一样,博主比较懒如果您想看题解的 ...

  9. Blocks题解(区间dp)

    Blocks题解 区间dp 阅读体验...https://zybuluo.com/Junlier/note/1289712 很好的一道区间dp的题目(别问我怎么想到的) dp状态 其实这个题最难的地方 ...

  10. 题解——洛谷P4767 [IOI2000]邮局(区间DP)

    这题是一道区间DP 思维难度主要集中在如何预处理距离上 由生活经验得,邮局放在中间显然最优 所以我们可以递推求出\( w[i][j] \)表示i,j之间放一个邮局得距离 然后设出状态转移方程 设\( ...

随机推荐

  1. Python学习成绩>=90分的同学用A表示,60-89分之间的用B表示,60分以下的用C表示。

    def SlowSnail(score): name = input('请输入姓名:') if score >= 90: grade = 'A' elif score >= 60: gra ...

  2. c语言实现this指针效果

    概要 由于目前在做一个比较复杂的嵌入式项目,想要借此提升一下代码的结构设计能力,所以想要以面向对象的思想来完成这个项目,即把每个板载外设资源视为一个对象,采用msp+bsp的模式,对每个bsp外设实现 ...

  3. v0.12.0-敏感词/脏词词标签能力进一步增强

    拓展阅读 敏感词工具实现思路 DFA 算法讲解 敏感词库优化流程 java 如何实现开箱即用的敏感词控台服务? 各大平台连敏感词库都没有的吗? v0.10.0-脏词分类标签初步支持 v0.11.0-敏 ...

  4. 数字孪生和GIS融合为环境保护领域带来的变化

    在当今日益关注环境保护和可持续发展的时代,数字孪生和GIS的融合为环境保护领域带来了巨大的变化.数字孪生是一种以3D模型为基础的仿真技术,能够对真实世界进行精确的建模和模拟,而GIS则是一种用于收集. ...

  5. windows10更新文件存在哪里

    windows10更新文件存在哪里windows10更新文件存在哪里 电脑系统每次更新都会有相应的更新文件,很多win10用户都想知道电脑更新文件存在哪里,其实这个很好找的. 你先双击此电脑进入,然后 ...

  6. Windows Server 2016配置NTP客户端

    前提:开通Windows Time 服务 输入services.msc进入服务管理界面,找到Windows Time 开启服务. 情况1:可以直接设置NTP时钟 控制面板--时钟和区域--设置时间和日 ...

  7. CentOS系统中yum的基本用法

    最小化安装系统时,yum可能会因为网卡配置问题,随机启动配置,导致无法使用, 在shell里面输入:yum --help ,结果显示 yum 已经正常安装了!!到底是哪里出了问题呢?经过网友的提示,我 ...

  8. HTTP 代理服务器的设计与实现(C++)

    实验内容 设计并实现一个基本 HTTP 代理服务器.要求在指定端口(例如 8080)接收来自客户的 HTTP 请求并且根据其中的 URL 地址访问该地址 所指向的 HTTP 服务器(原服务器),接收 ...

  9. Java 8升级Java 11,升级必知要点!竟然有这些坑…

    随着技术的不断进步,Java作为一种广泛使用的编程语言,其版本更新带来了许多新特性和性能提升.从Java 8升级到Java 11,是一个重要的转变,它不仅带来了新的编程范式,还引入了对现代软件开发的多 ...

  10. 2023-11-29:用go语言,给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。 需保证 返回结果的字典序最小。 要求不能打乱其他字符的相对位置)。 输入:s = “cba

    2023-11-29:用go语言,给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次. 需保证 返回结果的字典序最小. 要求不能打乱其他字符的相对位置). 输入:s = &quo ...