参考博文:区间dp小结(附经典例题)
首先,什么是区间dp?它是干什么的?

  1. 先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解
  2. 操作往往涉及到区间合并问题

以上。


模板如下:

 //mst(dp,0) 初始化DP数组
for(int i=;i<=n;i++)
{
dp[i][i]=初始值
}
for(int len=;len<=n;len++) //区间长度
for(int i=;i<=n;i++) //枚举起点
{
int j=i+len-; //区间终点
if(j>n) break; //越界结束
for(int k=i;k<j;k++) //枚举分割点,构造状态转移方程
{
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+][j]+w[i][j]);
}
}

注意区间的枚举起点。


例题1:51Nod1021 石子合并
题意:
N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。

首先,假如合并的次序没有限制,那么我们将每堆石子看成一个叶结点,每次合并的代价为数中结点,采用贪心的策略,则整个合并过程就是一个哈夫曼树的建树过程。

但是这里合并的时候要求每次只能合并相邻的两堆,则贪心这里就会出错了,因此我们采用dp的思想进行求解。

我们用dp[i][j]来表示合并第i堆到第j堆石子的最小代价,那么状态转移方程为:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);

其中w[i][j]表示把两部分合并起来的代价,即从第i堆到第j堆石子个数的和,为了方便查询,我们可以用sum[i]表示从第1堆到第i堆的石子个数和,那么w[i][j]=sum[j]-sum[i-1].

代码如下:

 #include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--) typedef long long ll;
const int maxn = ;
const ll mod = 1e9+;
const ll INF = 1e18;
const double eps = 1e-; int n,x;
int sum[maxn];
int dp[maxn][maxn]; int main()
{
while(~scanf("%d",&n))
{
sum[]=;
mst(dp,0x3f);
for(int i=;i<=n;i++)
{
scanf("%d",&x);
sum[i]=sum[i-]+x;
dp[i][i]=;
}
for(int len=;len<=n;len++)
for(int i=;i<=n;i++)
{
int j=i+len-;
if(j>n) continue;
for(int k=i;k<j;k++)
{
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+][j]+sum[j]-sum[i-]);
}
}
printf("%d\n",dp[][n]);
}
return ;
}

当然有的时候直接这样裸着O(n^3)会T,所以我们有平行四边形优化版本:

由于状态转移时是三重循环的,我们想能否把其中一层优化呢?尤其是枚举分割点的那个,显然我们用了大量的时间去寻找这个最优分割点,所以我们考虑把这个点找到后保存下来

用s[i][j]表示区间[i,j]中的最优分割点,那么第三重循环可以从[i,j-1)优化到【s[i][j-1],s[i+1][j]】。(这个时候小区间s[i][j-1]和s[i+1][j]的值已经求出来了,然后通过这个循环又可以得到s[i][j]的值)。

代码如下:

 #include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--) typedef long long ll;
const int maxn = ;
const ll mod = 1e9+;
const ll INF = 1e18;
const double eps = 1e-; int n,x;
int sum[maxn];
int dp[maxn][maxn];
int s[maxn][maxn]; int main()
{
while(~scanf("%d",&n))
{
sum[]=;
mst(dp,0x3f);
for(int i=;i<=n;i++)
{
scanf("%d",&x);
sum[i]=sum[i-]+x;
dp[i][i]=;
s[i][i]=i;
}
for(int len=;len<=n;len++)
for(int i=;i<=n;i++)
{
int j=i+len-;
if(j>n) continue;
for(int k=s[i][j-];k<=s[i+][j];k++)
{
if(dp[i][k]+dp[k+][j]+sum[j]-sum[i-]<dp[i][j])
{
dp[i][j]=dp[i][k]+dp[k+][j]+sum[j]-sum[i-];
s[i][j]=k;
}
}
}
printf("%d\n",dp[][n]);
}
return ;
}

例题2:hdu3506 猴子派对
题意:
问题转化后其实就是环形石子合并,即现在有围成一圈的若干堆石子,其他条件跟其那面那题相同,问合并所需最小代价。
解法:
把前n-1堆石子一个个移到第n个后面,那样环就变成了线,即现在有2*n-1堆石子需要合并,我们只要求下面的式子即可。求法与上面那题完全一样。

这道题在uestc版本上不做平行四边形优化会T。

 #include<cstdio>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int inf = ;
const int maxn = ;
int a[maxn];
int sum[maxn], dp[maxn][maxn], s[maxn][maxn]; int main() {
int n;
while (scanf("%d", &n) != EOF) {
for (int i = ; i <= n; i++) {
scanf("%d", &a[i]);
a[n + i] = a[i];
}
for (int i = ; i <= * n; i++)
sum[i] = sum[i - ] + a[i];
for (int m = ; m <= n; ++m) {
for (int i = ; i + m - < * n; ++i) {
int j = i + m - ;
if (m == ) {
dp[i][i] = ;
s[i][i] = i;
}
else {
dp[i][j] = inf;
for (int k = s[i][j - ]; k <= s[i + ][j]; ++k) {
if (dp[i][j] > dp[i][k] + dp[k + ][j] + sum[j] - sum[i - ]) {
dp[i][j] = dp[i][k] + dp[k + ][j] + sum[j] - sum[i - ];
s[i][j] = k;
}
}
}
}
}
int ans = inf;
for (int i = ; i <= n; ++i) {
ans = min(ans, dp[i][n + i - ]);
}
printf("%d\n", ans);
}
return ;
}

例题3:hdu5115 Dire wolf
题意:
有一排狼,每只狼有一个伤害A,还有一个伤害B。杀死一只狼的时候,会受到这只狼的伤害A和这只狼两边的狼的伤害B的和。如果某位置的狼被杀,那么杀它左边的狼时就会收到来自右边狼的B,因为这两只狼是相邻的了。求杀掉一排狼的最小代价。

解法:
设dp[i][j]为消灭编号从i到j只狼的代价,那么结果就是dp[1][n],枚举k作为最后一只被杀死的狼,此时会受到a[k]和b[i-1] b[j+1]的伤害,取最小的即可

可列出转移方程:
dp[i][j]=min(dp[i][j], dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
dp[i][i]=a[i]+b[i-1]+b[i+1];

代码如下:

 #include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
const int N=
const int INF=0xfffffff int main()
{
int T, n, a[N], b[N], dp[N][N], t=;
scanf("%d", &T);
while(T--)
{
memset(a, , sizeof(a));
memset(b, , sizeof(b));
scanf("%d", &n);
for(int i=; i<=n; i++)
scanf("%d", &a[i]);
for(int i=; i<=n; i++)
scanf("%d", &b[i]);
for(int i=; i<=n; i++)
{
for(int j=i; j<=n; j++)
dp[i][j]=INF;
/// dp[i][i]=a[i]+b[i-1]+b[i+1];
}
for(int l=; l<=n; l++)//注意边界
{
for(int i=; i+l<=n; i++)
{
int j=i+l;
for(int k=i; k<=j; k++)
{
dp[i][j]=min(dp[i][j], dp[i][k-]+dp[k+][j]+a[k]+b[i-]+b[j+]);
}
}
}
printf("Case #%d: %d\n", t++, dp[][n]);
}
return ;
}

区间dp(模板+例题)的更多相关文章

  1. 区间dp入门+例题

    区间dp作为线性dp的一种,顾名思义是以区间作为阶段进行dp的,使用它的左右端点描述每个维度,决策往往是从小状态向大状态转移中推得的.它跟st表等树状结构有着相似的原理---向下划分,向上递推. dp ...

  2. 石子合并 区间dp模板

    题意:中文题 Description 在操场上沿一直线排列着 n堆石子.现要将石子有次序地合并成一堆.规定每次只能选相邻的两堆石子合并成新的一堆, 并将新的一堆石子数记为该次合并的得分.允许在第一次合 ...

  3. 石子归并(区间dp 模板)

    区间dp入门 #include<iostream> #include<cstdio> #include <cctype> #include<algorithm ...

  4. A - Brackets POJ - 2955 (区间DP模板题)

    题目链接:https://cn.vjudge.net/contest/276243#problem/A 题目大意:给你一个字符串,让你求出字符串的最长匹配子串. 具体思路:三个for循环暴力,对于一个 ...

  5. 石子合并 区间DP模板题

    题目链接:https://vjudge.net/problem/51Nod-1021 题意 N堆石子摆成一条线.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石 ...

  6. 石子合并(区间dp典型例题)

    Description 有n堆石子排成一行,每次选择相邻的两堆石子,将其合并为一堆,记录该次合并的得分为两堆石子个数之和.已知每堆石子的石子个数,求当所有石子合并为一堆时,最小的总得分. Input ...

  7. 石子合并(区间DP经典例题)

    题目链接:https://www.luogu.org/problemnew/show/P1880 #include <cstdio> #include <cmath> #inc ...

  8. 区间dp学习笔记

    怎么办,膜你赛要挂惨了,下午我还在学区间\(dp\)! 不管怎么样,计划不能打乱\(4\)不\(4\).. 区间dp 模板 为啥我一开始就先弄模板呢?因为这东西看模板就能看懂... for(int i ...

  9. 区间dp板子题:[noi1995]石子合并

    非常经典的区间dp模板 对于每一个大于二的区间 我们显然都可以将它拆分成两个子序列 那么分别计算对于每个取最优值即可 #pragma GCC optimize("O2") #inc ...

  10. POJ2955--Brackets 区间DP入门 括号匹配

    题意很简单,就是求给出串中最大的括号匹配数目.基础题,格式基本为简单区间dp模板. #include<iostream> #include<string.h> using na ...

随机推荐

  1. django后台处理前端上传和显示图片

      1:项目根目录存放图片的目录 2:settings.py  添加 MEDIA_ROOT = os.path.join(BASE_DIR, "media") 3:url.py 添 ...

  2. Cacti 升级

    现在用的 cacti 1.0.3   决定升级一下cacti到最新版本 1.1.1   官方升级指导文件 Upgrading Cacti Backup the old Cacti database. ...

  3. 学习shiro最佳实践,绝对正确

    按照https://blog.csdn.net/qq_34021712/column/info/26947学习,基本能解决shiro一切问题,谢谢该博主

  4. Jmeter 连接Redis获取数据集

    公司开展了新的业务活动,需要配合其他部门做压测,由于脚本中的手机号和用户的uid需要参数化而且每次均不能重复,最初的考虑使用csv的方式来获取数据,比较头疼的问题是集群节点需要维护测试数据,所以我将所 ...

  5. python新手如何编写一个猜数字小游戏

    此文章只针对新手,希望大家勿喷,感谢!话不多说先上代码: import random if __name__ == '__main__': yourname = input("你好! 你的名 ...

  6. 联合索引在B+树上的存储结构及数据查找方式

    能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注编程大道公众号,让我们一同坚持心中所想,一起成长!! 引言 上一篇文章<MySQL索引那些事>主要讲了MySQL索引的底层原理,且对比了B ...

  7. 你一定看得懂的 DDD+CQRS+EDA+ES 核心思想与极简可运行代码示例

    前言 随着分布式架构微服务的兴起,DDD(领域驱动设计).CQRS(命令查询职责分离).EDA(事件驱动架构).ES(事件溯源)等概念也一并成为时下的火热概念,我也在早些时候阅读了一些大佬的分析文,学 ...

  8. Java数据结构--单链表

    #java学习经验总结------单链表的建立与结点的增删 在该链表结点有data数据,并且还有cpu,分给cpu随机的时间片,根据时间片大小进行结点data的排序 链表结点的建立 class Lin ...

  9. c语言心形告白代码实现

    c语言心形告白代码实现 1.彩色告白 include<stdio.h> include<math.h> include<windows.h> include< ...

  10. 网易MuMu模拟器不显示Menu(菜单)键的解决办法

    解决方法一: 前提:需要一个键盘 步骤: 1.直接按下键盘上的Menu键. 解决方法二: 前提:需要Root之后的文件浏览器 步骤: 1.在文件管理器中打开 /System 文件夹: 2.复制 bui ...