区间DP小结 及例题分析:P1880 [NOI1995]石子合并,P1063 能量项链
区间类动态规划
一.基本概念
区间类动态规划是线性动态规划的拓展,它在分阶段划分问题时,与阶段中元素出现的顺序和由前一阶段的那些元素合并而来由很大的关系。例如状态f [ i ][ j ],它表示以已合并的次数为阶段,以区间的左端点 i 为状态,它的值取决于第 i 个元素和第 j 个元素断开的位置 k,即 f [ i ][ k ] + f [ k+1 ][ j ]的值。这一类型的动态规划,阶段特征非常明显,求最优值时需要预先设置阶段内的区间统计值,还要以动态规划的起始位置来判断。
区间类动态规划的特点:
合并:即将两个或多个部分进行整合,当然也可以反过来,也就是对一个问题分解成两个或多个部分。
特征:能将问题分解成为两两合并的形式。
求解:对整个问题设最优值,枚举合并点,将问题分解成左右两个部分,最后合并左右两个部分的最优值得到原问题的最优值。有点类似分治算法的解题思想。
for(int len=;len<=n;len++) //枚举区间长度
for(int i=;i<=n-len+;i++) //枚举左端点
{
j=i+len-; //右端点
for(int k=i;k<j;k++) //枚举中间断点
{
f[i][j]=max(f[i][k]+f[k+][j],f[i][j]); //最大值状态转移方程
f[i][j]=min(f[i][k]+f[k+][j],f[i][j]); //最小值状态转移方程
}
}
二.例题分析
P1880 [NOI1995]石子合并

首先很多人第一眼看这个题会误以为是贪心,毕竟样例贪心分析也是如此,但其实不是贪心,而是区间DP;
正解:
若最初的第 l 堆石子和第 r 堆石子被合并成一堆,则说明 l~r 之间的每堆石子也已经被合并,这样 l 和 r 才有可能相邻。因此,在任意时刻,任意一堆石子均可以用一个闭区间 [ l,r ]来描述,表示这堆石子是由最初的第 l~r 堆石子合并而成的,其重量为∑ ai (l <= i <= r)。另外,一定存在一个整数 k (l <=k <r)在这堆石子形成之前,先有第 l~k 堆石子(闭区间 [ l,k ])被合并成一堆,第 k+1~r 堆石子(闭区间 [ k+1,r ])被合并成一堆,然后这两堆石子在合并成 [ l,r ]。
对应到动态规划中,就意味着两个长度较小的区间上的信息向一个更长区间发生了转移,划分点 k 就是转移的决策。自然地,应该把区间长度len 作为DP的阶段。不过,区间长度可以由左端点和右端点表示,即len=r - l + 1。本着动态规划“选择最小的能覆盖状态空间的维度集合”的思想,我们可以只用左右端点表示DP的状态。
设 sum [ i ]表示从1堆石子到第 i 堆石子数总和,sum [ i ] = a[ 1 ] + a[ 2 ] +……+a[ i ];(维护一个前缀和)
fmaxn [ i ][ j ]表示从第 i 堆石子合并到第 j 堆石子的最大得分;
fminx [ i ][ j ]表示从第 i 堆石子合并到第 j 堆石子的最小得分;
状态转移方程就是:
fmaxn [ i ][ j ] = max(fmaxn [ i ][ k ] + fmaxn [ k+1 ][ j ],fmaxn [ i ][ j ])+ sum [ j ] - sum [ i - 1 ];
fminx [ i ][ j ] = max(fminx [ i ][ k ] + fminx [ k+1 ][ j ],fminx [ i ][ j ])+ sum [ j ] - sum [ i - 1 ];
初始条件:fmaxn [ i ][ i ] = 0,fminx [ i ][ i ] = INF;
时间复杂度:O(n3)。
环的处理
题目中石子围成了一个圈,而不是一条线,那么我们怎么处理呢?
方法一:由于石子堆围成一圈,因此我们可以枚举分开的位置,首先将这个圈转化为链,因此要做n次,时间复杂度为:O(n4);
方法二:我们可以将这条链延长两倍,扩展成2n-1堆,其中第1堆与第 n+1 堆完全相同,第 i 堆和第 n+i 堆完全相同,这样我们只要对这2n堆动态规划以后,枚举f [ 1 ][ n ],f [ 2 ][ n+1 ]……f [ n ][ 2n- 1 ]取最优值即可。时间复杂度为:O(8n3)。
代码如下:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int read()
{
char ch=getchar();
int a=,x=;
while(ch<''||ch>'')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>=''&&ch<='')
{
a=(a<<)+(a<<)+(ch-'');
ch=getchar();
}
return a*x;
}
int n,a[],fminx[][],fmaxn[][];
//a数组是每堆石子的重量;
//fminx[i][j]是将第i堆石子合并到第j堆石子的最小得分
//fmaxn[i][j]是将第i堆石子合并到第j堆石子的最大得分
long long sum[]; //前缀和,第1堆石子到第i堆石子的重量和
int main()
{
n=read(); //n堆石子
for(int i=;i<=n;i++)
{
a[i]=read(); //石子重量
a[n+i]=a[i]; //将链延长两倍
}
for(int i=;i<=n*;i++) //注意这里就是2n了
{
sum[i]=sum[i-]+a[i]; //预处理前缀和
fminx[i][i]=; //边界条件
fmaxn[i][i]=;
}
for(int i=;i<=n;i++) //枚举区间长度,以合并的堆数作为阶段
{
for(int j=;i<=*n-j+;j++)//枚举区间左端点
{
int r=i+j-; //区间右端点
fmaxn[j][r]=; //初始化
fminx[j][r]=1e9;
for(int k=j;k<r;k++) //枚举中间断点
{
fmaxn[j][r]=max(fmaxn[j][k]+fmaxn[k+][r],fmaxn[j][r]); //状态转移方程
fminx[j][r]=min(fminx[j][k]+fminx[k+][r],fminx[j][r]);
}
fmaxn[j][r]+=sum[r]-sum[j-]; //加上第j堆石子到第r堆石子的重量和,利用的就是前缀和
fminx[j][r]+=sum[r]-sum[j-];
}
}
int minx=1e9,maxn=-1e9;
for(int i=;i<=n;i++)
{
minx=min(minx,fminx[i][i-+n]); //找最后答案
maxn=max(maxn,fmaxn[i][i-+n]);
}
printf("%d\n%d",minx,maxn); //输出
return ;
}
P1063 能量项链


很多同学误把此题理解成确定第一颗珠子后,必须按顺序合并所有的珠子,用贪心的策略,逐一枚举所有珠子为第一颗,选择总能量最大的方案数输出。恰好样例也符合这种逻辑,导致很多同学只得了30分。其实这道题并未要求按顺序合并,事实说明按顺时针方向摆放珠子,合并是可以选择任意相邻两个珠子合并,只要总能量最大即可。
我们用head [ i ]表示第 i 颗珠子的头标记,用 tail [ i ]表示第 i 颗珠子的尾标记,合并两颗相邻珠子所释放的能量为:
Energy=head [ i ] * tail [ i ] * head [ i+1 ];
合并时不一定按照输入的顺序合并,与石子合并问题类似,可以归结到第n-1次合并,具有明显的动态规划性质。设f [ i ][ j ]表示从第 i 颗珠子合并到第 j 颗珠子所释放的最大能量。假设最后合并的位置 k 和 k+1,则有:
f [ i ][ j ] = max(f [ i ][ k ] + f [ k+1 ][ j ] + head [ i ] * tail [ k ] * head [ j ]),i <= k < j <= n;
上式中,f [ i ][ k ]表示合并第 i 颗珠子到第 k 颗珠子产生的最大能量,f [ k+1 ][ j ]表示合并第 k+1 颗珠子到第 j 颗珠子产生的最大能量,head [ i ] * tail [ k ] * tail [ j ]表示最后一次合并产生的能量。
初始化:f [ i ][ i ] = 0,ans = max{ f [ 1 ][ n ],f [ 2 ][ n+1 ],f [ 3 ][ n+2 ],……,f [ n ][ 2n-1 ] };
时间复杂度:O(8n3)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int read() //读入优化
{
char ch=getchar();
int a=,x=;
while(ch<''||ch>'')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>=''&&ch<='')
{
a=(a<<)+(a<<)+(ch-'');
ch=getchar();
}
return a*x;
}
int n;
int head[],tail[],f[][];
//head是每个珠子的头标记,tail是每个珠子的尾标记;
//f[i][j]表示合并第i颗珠子到第j颗珠子所产生的最大能量
//这里注意要开两倍大小哦
int main()
{
n=read();
for(int i=;i<=n;i++)
{
head[i]=read();
head[n+i]=head[i]; //延长两倍
}
for(int i=;i<=*n-;i++) tail[i]=head[i+]; //前一个的尾标记就是后一个的头标记
tail[*n]=head[]; //最后一个的尾标记就是第一个的头标记
for(int i=;i<=n*-;i++) f[i][i]=; //初始化
for(int i=;i<=n-;i++) //枚举合并次数
{
for(int j=;i+j<=n*;j++) //枚举合并的区间长度
{
int r=i+j; //区间右端点
for(int k=j;k<r;k++) //枚举中间断点
f[j][r]=max(f[j][r],f[j][k]+f[k+][r]+head[j]*tail[k]*tail[r]); //状态转移方程
}
}
int ans=;
for(int i=;i<=n;i++)
{
ans=max(ans,f[i][i-+n]); //以每个珠子为第一颗珠子,找最大能量
}
printf("%d",ans);
return ;
}
就整理到这里吧~如果有不对的地方请在下方评论哦QwQ~
区间DP小结 及例题分析:P1880 [NOI1995]石子合并,P1063 能量项链的更多相关文章
- 【区间dp】- P1880 [NOI1995] 石子合并
记录一下第一道ac的区间dp 题目:P1880 [NOI1995] 石子合并 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 代码: #include <iostream> ...
- P1880 [NOI1995]石子合并[区间dp+四边形不等式优化]
P1880 [NOI1995]石子合并 丢个地址就跑(关于四边形不等式复杂度是n方的证明) 嗯所以这题利用决策的单调性来减少k断点的枚举次数.具体看lyd书.这部分很生疏,但是我还是选择先不管了. # ...
- P1880 [NOI1995]石子合并 区间dp
P1880 [NOI1995]石子合并 #include <bits/stdc++.h> using namespace std; ; const int inf = 0x3f3f3f3f ...
- 洛谷 P1880 [NOI1995]石子合并 题解
P1880 [NOI1995]石子合并 题目描述 在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试 ...
- P1880 [NOI1995]石子合并 区间dp+拆环成链
思路 :一道经典的区间dp 唯一不同的时候 终点和起点相连 所以要拆环成链 只需要把1-n的数组在n+1-2*n复制一遍就行了 #include<bits/stdc++.h> usi ...
- 洛谷 P1880 [NOI1995] 石子合并(区间DP)
传送门 https://www.cnblogs.com/violet-acmer/p/9852294.html 题解: 这道题是石子合并问题稍微升级版 这道题和经典石子合并问题的不同在于,经典的石子合 ...
- 区间DP初探 P1880 [NOI1995]石子合并
https://www.luogu.org/problemnew/show/P1880 区间dp,顾名思义,是以区间为阶段的一种线性dp的拓展 状态常定义为$f[i][j]$,表示区间[i,j]的某种 ...
- HDU4632 Poj2955 括号匹配 整数划分 P1880 [NOI1995]石子合并 区间DP总结
题意:给定一个字符串 输出回文子序列的个数 一个字符也算一个回文 很明显的区间dp 就是要往区间小的压缩! #include<bits/stdc++.h> using namesp ...
- P1880 [NOI1995]石子合并【区间DP】
题目描述 在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试设计出1个算法,计算出将N堆石子合并成1 ...
随机推荐
- 区间第k大问题 权值线段树 hdu 5249
先说下权值线段树的概念吧 权值平均树 就是指区间维护值为这个区间内点出现次数和的线段树 用这个加权线段树 解决第k大问题就很方便了 int query(int l,int r,int rt,int k ...
- (一)WebService基础
一.SOA架构 先给出一个概念 SOA ,即Service Oriented Architecture ,中文一般理解为面向服务的架构, 既然说是一种架构的话,所以一般认为 SOA 是包 ...
- 数据格式转换string.Format
1.格式化货币(跟系统的环境有关,中文系统默认格式化人民币,英文系统格式化美元) string.Format("{0:C}",0.2) 结果为:¥0.20 (英文操作系统结果:$0 ...
- Django-2.0 汉化
1.语言 LANGUAGE_CODE = 'zh-hans' 2.时区 TIME_ZONE = 'Asia/Shanghai' 3.字段名汉化 models.CharFielf(verbose_nam ...
- ubantu18.04 配置nginx与uwsgi(前后端分离)
ubantu18.04 配置nginx与uwsgi 一.首先先安装nginx静态服务 先更新 sudo apt-get update 1.安装gcc g++的依赖库 sudo apt-get in ...
- 前端知识总结--css用div画环形圆
如何用最少的div画最多的环形?如下图所示最少需要多少个div? 暂时想到的利用div的边框.内外阴影及befor和after的伪元素实现 以下代码可以实现上图效果: <style> di ...
- 【数字图像处理】帧差法与Kirsch边缘检测实现运动目标识别与分割
本文链接:https://blog.csdn.net/qq_18234121/article/details/82763385 作者:冻人的蓝鲸梁思成 视频分割算法可以从时域和空域两个角度考虑.时域分 ...
- [转]Spring Security Oauth2 认证流程
1.本文介绍的认证流程范围 本文主要对从用户发起获取token的请求(/oauth/token),到请求结束返回token中间经过的几个关键点进行说明. 2.认证会用到的相关请求 注:所有请求均为po ...
- 程哥带你学python-[第一章-初识Python]
Python是一种解释型.面向对象.动态数据类型的高级程序设计语言. Python由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年. 像Perl语言一样, Pyt ...
- TreeMap源码实现类中文全解析
/** * 基于红黑树(Red-Black tree)的 NavigableMap 实现.该映射根据其键的自然顺序进行排序, * 或者根据创建映射时提供的Comparator 进行排序,具体取决于使用 ...