区间DP

P1063 能量项链

题目描述
  • 给定一串首尾相连的能量珠串
  • 按照该计算规则进行合并:如果前一颗能量珠的头标记为\(m\),尾标记为\(r\),后一颗能量珠的头标记为\(r\),尾标记为\(n\),则聚合后释放的能量为\(m \times r \times n\),新产生的珠子的头标记为\(m\),尾标记为\(n\)。
  • 求最终合并为一个珠子的时候释放的能量的最大值
思路分析
  • 首先因为只是一个串串,所以我们肯定不好弄,所以我们可以生成一个线性的区间来代替这个串串
  • 那么根据这个规则,我们分为好几个小区间进行\(dp\),类似于弗洛伊德的算法,枚举不同长度的区间进行比较大小
  • 我们很显然的可以知道从\(i~n+i-1\)是一个完整的能量珠串(减去\(1\)是因为他自己已经有了),所以我们可以根据这个做dp了
  • 枚举长度\(k\),所以我们可以看出在\(i~i+k\)这个区间内,我们可以找一个点\(j\)来分割开,相当于已经合并完的两个珠子\(i~j\)和\(j+1~i+k\),最终在进行合并
状态转移方程设计
  • 设\(f_{i,j}为合并第\)i~j$个石子的最优值
  • 所以我们根据思路可以推导出式子
\[f[i,i+k]=max(f[i,i+k],f[i,j]+f[j+1,i+k]+left_i\times right_j\times right_{i+k})\ \ (j\in [i,i+k))
\]
代码实现
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=410;
int f[N][N];
int n;
struct node{
int x,y;
}a[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].x;
a[i-1].y=a[i].x;
}
a[n].y=a[1].x;
for(int i=1;i<n;i++)
a[n+i]=a[i];//使其成为一条链,方便操作
//i~i+n就可以看成一个完整的链
memset(f,0,sizeof(f));
//f[i][j]表示合并第i~j个石子的最优值
for(int k=1;k<=n;k++)//枚举长度
for(int i=1;i<2*n-k;i++)//从第i个石子开始断开,
//那么i~i+n就是一条线
for(int j=i;j<i+k;j++)
//在i~i+k中任意选一点j作为分界点,然后就可以分成
//i~j和j+1~i+k这两段
//首先这两段里面的都合并起来,然后最后(i,j)(j+1,i+k)端点序号为这两个的
//珠子在尽心合并
{
f[i][i+k]=max(f[i][i+k],f[i][j]+f[j+1][i+k]+a[i].x*a[j].y*a[i+k].y);
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,f[i][i+n-1]);
cout<<ans<<endl;
return 0;
}

P1880 [NOI1995]石子合并

题目描述

在一个圆形操场的四周摆放 \(N\) 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的\(2\)堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 \(N\) 堆石子合并成 \(1\) 堆的最小得分和最大得分。

思路分析
  • 和能量项链这个题目相类似,仍然是一个环形的区间dp,我们可以从其中的任意一堆石子开始操作
  • 我们可以把这个多开\(n\)的数组空间,将他弄成线性进行解决
状态转移方程设计
  • 区间dp,所以我们最常见的方法是状态转移方程设置为区间的形式

  • 最常见的做法为在一个大的区间内找两个区间并进行合并,求最大值

  • 这个题我们通过数据可以发现,当区间\([i,j]\)中两个小的区间\([i,k],[k+1,j]\)合并时,它这次合并的得分正好为第\(i\)堆到第\(j\)堆的石子的总个数

    所以我们设\(s_i\)为前\(i\)堆石子的前缀和

  • 我们可以设置\(f[i][j]表示从第\)i\(堆到第\)j$堆合并成为\(1\)堆时的区间最值,可以得到以下的状态转移方程式

\[f_{max}[i][j]=max(f_{max}[i][j],f_{max}[i][k]+f_{max}[k+1][j]+s_j-s_{i-1})\ \ \ \ (k\in [i,j))
\]
\[f_{min}[i][j]=min(f_{min}[i][j],f_{min}[i][k]+f_{min}[k+1][j]+s_j-s_{i-1})\ \ \ \ (k\in [i,j))
\]
代码实现
#include<iostream>
#include<queue>
#include<stack>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
#include<algorithm>
using namespace std;
const int N=5e2+9;
int a[N];
int n;
int s[N];//前缀和
int fmax[N][N],fmin[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i+n]=a[i];
}
for(int i=1;i<=n*2;i++)
s[i]=s[i-1]+a[i];
//memset(fmin,0x3f3f3f,sizeof(fmin));
for(int l=1;l<n;l++)
for(int i=1,j=i+l;(i<n+n)&&(j<n+n);i++,j=i+l)
{
fmin[i][j]=0x3f3f3f3f;
for(int k=i;k<j;k++)
{
fmax[i][j]=max(fmax[i][j],fmax[i][k]+fmax[k+1][j]+(s[j]-s[i-1]));
//两边i~k和k+1~j,最后一次合并的时候加起来得到的的分数正好是
//i~j的和
fmin[i][j]=min(fmin[i][j],fmin[i][k]+fmin[k+1][j]+(s[j]-s[i-1]));
}
}
int amax=0,amin=0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
amax=max(amax,fmax[i][i+n-1]);
amin=min(amin,fmin[i][i+n-1]);
}
cout<<amin<<endl;
cout<<amax<<endl;
return 0;
return 0;
}

P3146 [USACO16OPEN]248 G

题目描述

(一个因为翻译而WA的“毒瘤”题)

给定一个长度为\(n\)的区间,在区间内相邻的且数字大小相同的两个数字可以合并的到一个比它\(+1\)数字

询问可以合并成的最大数值为多少

思路分析
  • 一个线性区间dp,我们依旧是在区间内做处理

  • 在一个区间内,枚举长度,并在这个区间内找一个分割点,是这个点两边的数值是相等的,然后进行大小比较

状态转移方程设计
  • 我们可以设\(f[i][j]\)为区间\([i,j]\)内的合并出来的最大值

  • 由此可以得到状态转移方程(状态可以根据需要灵活变化,此方程取\(j\)为分界点)

\[f[i][i+k]=max(f[i][i+k],f[i][j]+1)\ \ (f[i][j]=f[j+1][i+k])
\]
代码实现
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=5e2+9;
int f[N][N];
int num[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>num[i];
f[i][i]=num[i];
}
int ans=0;
for(int k=1;k<=n;k++)//枚举区间长度
for(int i=1;i+k<=n;i++)//确保右端点在范围内
for(int j=i;j<i+k;j++)//保证分割的界限在范围内
{
if(f[i][j]==f[j+1][i+k])//判断两边是否相等
f[i][i+k]=max(f[i][i+k],f[i][j]+1);
//可以改成 f[i][i+k]=max(f[i][i+k],f[j+1][i+k]+1);
ans=max(f[i][i+k],ans);//在过程中找答案,节省时间
}
cout<<ans<<endl;
return 0;
}

P4170 [CQOI2007]涂色

题目描述
  • 一开始给你一个空序列,可以使一个字母连续覆盖相邻的任意等长的区间,求最少有几次可以得到目标状态
思路分析
  • 因为是字符串,输入的时候处理一下让他的编号从\(1\)开始,好进行处理
  • 首先,每个长度为\(1\)的区间都赋值为\(1\),因为他需要进行一次涂色
  • 其次,我们可以发现的是,当我们枚举一个区间时,如果左右端点是一样的,那么我们可以对左右端点分别做操作,比较一下左端点右移\(1\)的区间去覆盖左端点次数小还是右端点左移动\(1\)的区间去覆盖右端点所使用的次数少。
  • 最后进行正常的区间断点枚举,知道找出最小的方案为止
状态转移方程设计
  • 正常的状态转移方程,设\(f[i][j]\)为区间\([i,j]\)变成最终状态所需要的最小次数
  • 那么可以得到状态转移方程:
\[f[i][j]=min \begin{cases} f[i+1][j],f[i][j-1]\ \ \ \ (s[i]=s[j])
\\
\\ f[i][j],f[i][k]+f[k+1][j]\ \ \ \ (s[i]!=s[j])
\end{cases}\]
代码实现
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<algorithm>
#include<iostream>
using namespace std;
const int N=1e2+9;
int f[N][N];
char s[N];
int main()
{
cin>>(s+1);
memset(f,0x3f3f3f3f,sizeof(f));
for(int i=1;i<=strlen(s+1);i++)
f[i][i]=1;//开始可以被涂一次//bingo
for(int k=1;k<=strlen(s+1);k++)//枚举区间
{
for(int i=1;i+k<=strlen(s+1);i++)
{
if(s[i]==s[i+k])//两端点相等,所以我们就在首次涂色的时候多涂上一层,
//看看是涂到左边端点花费少还是涂到右边端点花费少
f[i][i+k]=min(f[i+1][i+k],f[i][i+k-1]);
else for(int j=i;j<i+k;j++)
f[i][i+k]=min(f[i][i+k],f[i][j]+f[j+1][i+k]);
//如果两个端点一样,那么就是两块区间相加求最小值
//因为当你左右两边不一样是,一定会左右均刷一次,
//然后对于中间的,就看一看是否有一样的就可以
//dp枚举可以考虑到上述情况
}
}
cout<<f[1][strlen(s+1)]<<endl;
return 0;
}

P4290 [HAOI2008]玩具取名

思路分析
  • 因为给的每一个转化的字符串是两个值,所以我们只需要通过枚举分别都可以被一个字母表示的两个小区间,然后看一下这两个字母是否可以被一个字母来代替,也就是找一找是否可以用一个字符来代替整个区间
状态转移方程设计
  • \(f[i][j][k]\)表示区间\([i,j]\)可以通过\(k\)转化过来
  • \(can[i][j][k]\)表示\(i,j\)可以通过\(k\)转化过来(\(z1+z2->z\))
\[f[i][i+k][z]=true\ \ \ (f[i][j][z1]=true,f[j+1][i+k][z2]=true,can[z][z1][z2]=true)
\]
代码实现
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=209;
bool f[N][N][5],can[5][5][5];//f表示区间[i,j]可以通过k转化过来
//can表示i,j可以通过k转化过来
int le[5];//存长度
char s[N],c[5];
int ques(char s)
{
if(s=='W') return 1;
if(s=='I') return 2;
if(s=='N') return 3;
if(s=='G') return 4;
}
int main()
{
for(int i=1;i<=4;i++) cin>>le[i];
for(int i=1;i<=4;i++)
{
for(int j=1;j<=le[i];j++)
{
cin>>c;
can[i][ques(c[0])][ques(c[1])]=true;//表示i可以从这俩转化过来
}
}
cin>>(s+1);
int len=strlen(s+1);
for(int i=1;i<=len;i++)
f[i][i][ques(s[i])]=true;
for(int k=1;k<=len;k++)
for(int i=1;i+k<=len;i++)
for(int j=i;j<i+k;j++)
for(int z=1;z<=4;z++)//枚举可以代替z1,z2的数
for(int z1=1;z1<=4;z1++)//枚举z1
for(int z2=1;z2<=4;z2++)//枚举z2
{
if(f[i][j][z1]&&f[j+1][i+k][z2]&&can[z][z1][z2])
//这个方程表示如果区间[i,j]可以被z1表示
//并且区间[j+1,i+k]可以被z2表示
//同时z可以与z1,z2转化
//那么[i,i+k]这个区间就可以被z来表示
f[i][i+k][z]=true;
}
bool flag=0;
if(f[1][len][1]) {flag=1,cout<<'W';};
if(f[1][len][2]) {flag=1,cout<<'I';};
if(f[1][len][3]) {flag=1,cout<<'N';};
if(f[1][len][4]) {flag=1,cout<<'G';};
if(!flag)
cout<<"The name is wrong!"<<endl;
return 0;
}

状压DP

P1896 [SCOI2005]互不侵犯

思路分析
  • 首先看一下数据范围,这是一个状态压缩动态规划,所以我们就考虑用二进制来进行处理
  • 我们可以发现,每一个位置的国王数量最多是\(1\),所以我们就可以用一个二进制串串来表示每一行的国王分布情况,
  • 一开始我们可以预处理一下每一行符合标准的状态\(situ_{i}\),然后把它这一行的国王数量\(sum_{i}\)和二进制串存到一个数组中,便于枚举
  • 然后我们考虑一下这一个状态如何转移
  • 按照题目中的条件所说的,每一个国王的上下左右及左上下,右上下都不可以放人!!!
    • $situ_j $ & \(situ_k\) 如果不为零,说明上下有交叉的
    • $situ_j $ & \(situ_k<<1\),如果不为零,说明右上放了人
    • \(situ_j<<1\) & \(situ_k\),如果不为零,说明左上方放了人
  • 这里还有一个小技巧就是,当我们枚举一行的时候,我们只需要考虑上一行是否符合这个状态就好了,下一行的状态可以转到下一行的时候在考虑这一行的状态来判断即可
状态转移方程设计、

设\(f[i][j][k]\)表示第\(i\)行,状态为第\(i\)行,状态为\(j\)时,前\(i\)行的一共放了\(k\)个国王的方案数

得到以下解题思路

\[f[i][j_1][k]=\sum f[i-1][j_2][k-sum[i]]
\]
代码实现
#include<iostream>
#include<cstdio>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<algorithm>
#define int long long
using namespace std;
const int N=11;
const int M=2009;
int n,num;
int cnt;//状态的指针
int situ[M];//可用的状态
int sum[M];//求每一个状态所包含的1的数量
int f[N][(1<<N)][N*N];//表示第i行,状态是j,放置了k个棋子时的状态...
void search(int he,int gs,int pif)//表示状态,表示1的个数,表示当前为第几位
{
if(pif>=n)
{
situ[++cnt]=he;
sum[cnt]=gs;
return;
}
search(he,gs,pif+1);//这个就是表示当前位数没有选,要选择与他相邻的位数
search (he+(1<<pif),gs+1,pif+2);//当前为要选的第pif位,所以就在第pif位上标上个1;
//表示在这个地方有一个国王
}
signed main()
{
cin>>n>>num;
search(0,0,0);
for(int i=1;i<=cnt;i++)
f[1][i][sum[i]]=1; //第二唯为状态的下标不是状态
for(int i=2;i<=n;i++)
for(int j=1;j<=cnt;j++)//枚举当前的状态
for(int k=1;k<=cnt;k++)//枚举上一个的状态
{
if(situ[j]&situ[k])continue;
if(situ[j]&(situ[k]<<1)) continue;
if((situ[j]<<1)&situ[k]) continue;
for(int l=num;l>=sum[j];l--)
f[i][j][l]+=f[i-1][k][l-sum[j]];
}
int ans=0;
for(int i=1;i<=cnt;i++)
ans+=f[n][i][num];
cout<<ans<<endl;
return 0;
}

DP 从棺材到入土的更多相关文章

  1. DP没入门就入土

    写在前面 记录最近刷的DP题 以及 打死都不可能想到状态设计DP系列 汇总 洛谷 P6082 [JSOI2015]salesman 树形\(\texttt{DP}\) + 优先队列 比较容易看出来这是 ...

  2. CSP-S2019 停课日记

    前言 不想上文化课,于是就停课了 (雾) \(10.13\) 停课前一天 今天名义上是放假,所以不算停课. 老师和同学们听说我要停课,都十分的不舍.我啥也没说就悄悄溜到一中来了. \(10.14\) ...

  3. 【DP入门到入土】

    DP例题较多,可以根据自己需求食用~ update:下翻有状压DP入门讲解,也只有讲解了(逃~ DP的实质,就是状态的枚举. 一般用DP解决的问题,都是求计数或最优问题,所以这类问题,我们也可以用搜索 ...

  4. ZOJ 3689 Digging(DP)

    Description When it comes to the Maya Civilization, we can quickly remind of a term called the end o ...

  5. BZOJ 1911: [Apio2010]特别行动队 [斜率优化DP]

    1911: [Apio2010]特别行动队 Time Limit: 4 Sec  Memory Limit: 64 MBSubmit: 4142  Solved: 1964[Submit][Statu ...

  6. 2013 Asia Changsha Regional Contest---Josephina and RPG(DP)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=4800 Problem Description A role-playing game (RPG and ...

  7. AEAI DP V3.7.0 发布,开源综合应用开发平台

    1  升级说明 AEAI DP 3.7版本是AEAI DP一个里程碑版本,基于JDK1.7开发,在本版本中新增支持Rest服务开发机制(默认支持WebService服务开发机制),且支持WS服务.RS ...

  8. AEAI DP V3.6.0 升级说明,开源综合应用开发平台

    AEAI DP综合应用开发平台是一款扩展开发工具,专门用于开发MIS类的Java Web应用,本次发版的AEAI DP_v3.6.0版本为AEAI DP _v3.5.0版本的升级版本,该产品现已开源并 ...

  9. BZOJ 1597: [Usaco2008 Mar]土地购买 [斜率优化DP]

    1597: [Usaco2008 Mar]土地购买 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4026  Solved: 1473[Submit] ...

随机推荐

  1. Spring AOP 实战运用

    Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体 ...

  2. 新下载的Chrome 不能用,设置搜索引擎,谷歌浏览器不能用,chrome浏览器不能用,google chrome 不能用

    新下载的chrome默认搜索引擎 是google搜索,而google搜索引擎在国内是不能使用的,要设置为 百度或.360.搜狗搜索引擎才能使用. 设置方法如下: 1.打开 Chrome. 2.点击右上 ...

  3. JavaSwing 船只停靠管理可视化(三)

    JavaSwing 船只停靠管理可视化(一) JavaSwing 船只停靠管理可视化(二) JavaSwing 船只停靠管理可视化(三) JavaSwing 船只停靠管理可视化(四) JavaSwin ...

  4. JavaSwing 船只停靠管理可视化(二)

    JavaSwing 船只停靠管理可视化(一) JavaSwing 船只停靠管理可视化(二) JavaSwing 船只停靠管理可视化(三) JavaSwing 船只停靠管理可视化(四) JavaSwin ...

  5. JSP 的 4 种作用域?

    page:代表与一个页面相关的对象和属性. request:代表与客户端发出的一个请求相关的对象和属性.一个请求可能跨越多个页面,涉及多个 Web 组件:需要在页面显示的临时数据可以置于此作用域. s ...

  6. JDBC删除

    1 if(conn != null){ 2 String temps="3"; 3 conn.setAutoCommit(false); 4 PreparedStatement p ...

  7. volatile实现原理--为什么实现了可见性却不能保证原子性

    本篇文章我们来解决一个问题  这也是面试面的比较多的问题,进阶阶段(高级)一般都会问到. volatile变量怎么保证可见性  为什么在并发情况下无法保证原子性? 比较懒了  摘了一段JVM原理的片段 ...

  8. Linux 时间同步 01 简介

    Linux 时间同步 01 简介 目录 Linux 时间同步 01 简介 时间同步 公共NTP服务器地址及IP 系统时间相关文件 时间同步 大数据产生与处理系统是各种计算设备集群的,计算设备将统一.同 ...

  9. python安装库报错的处理方法

    在安装python map库时遇到了还多问题,找了好的方法都没有安装成功,最后改安装basemap库参考了了:https://www.jb51.net/article/147780.htm一文操作,最 ...

  10. Kubernetes官方java客户端之二:序列化和反序列化问题

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...