DP 从棺材到入土
区间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$个石子的最优值
- 所以我们根据思路可以推导出式子
\]
代码实现
#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\)堆时的区间最值,可以得到以下的状态转移方程式
\]
\]
代码实现
#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\)为分界点)
\]
代码实现
#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],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\))
\]
代码实现
#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\)个国王的方案数
得到以下解题思路
\]
代码实现
#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 从棺材到入土的更多相关文章
- DP没入门就入土
写在前面 记录最近刷的DP题 以及 打死都不可能想到状态设计DP系列 汇总 洛谷 P6082 [JSOI2015]salesman 树形\(\texttt{DP}\) + 优先队列 比较容易看出来这是 ...
- CSP-S2019 停课日记
前言 不想上文化课,于是就停课了 (雾) \(10.13\) 停课前一天 今天名义上是放假,所以不算停课. 老师和同学们听说我要停课,都十分的不舍.我啥也没说就悄悄溜到一中来了. \(10.14\) ...
- 【DP入门到入土】
DP例题较多,可以根据自己需求食用~ update:下翻有状压DP入门讲解,也只有讲解了(逃~ DP的实质,就是状态的枚举. 一般用DP解决的问题,都是求计数或最优问题,所以这类问题,我们也可以用搜索 ...
- ZOJ 3689 Digging(DP)
Description When it comes to the Maya Civilization, we can quickly remind of a term called the end o ...
- BZOJ 1911: [Apio2010]特别行动队 [斜率优化DP]
1911: [Apio2010]特别行动队 Time Limit: 4 Sec Memory Limit: 64 MBSubmit: 4142 Solved: 1964[Submit][Statu ...
- 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 ...
- AEAI DP V3.7.0 发布,开源综合应用开发平台
1 升级说明 AEAI DP 3.7版本是AEAI DP一个里程碑版本,基于JDK1.7开发,在本版本中新增支持Rest服务开发机制(默认支持WebService服务开发机制),且支持WS服务.RS ...
- AEAI DP V3.6.0 升级说明,开源综合应用开发平台
AEAI DP综合应用开发平台是一款扩展开发工具,专门用于开发MIS类的Java Web应用,本次发版的AEAI DP_v3.6.0版本为AEAI DP _v3.5.0版本的升级版本,该产品现已开源并 ...
- BZOJ 1597: [Usaco2008 Mar]土地购买 [斜率优化DP]
1597: [Usaco2008 Mar]土地购买 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 4026 Solved: 1473[Submit] ...
随机推荐
- AWS中国区的那些“坑”
前言 最近做的一个项目是要把公司在国外已经上线的一个物联网的项目移植到AWS中国区来. 由于AWS属于国外云产商,在中国运营,必须符合国家的相关规定: 必须是合资公司 (AWS北京区由北京光环新网运营 ...
- FPT: Feature Pyramid Transfomer
导言: 本文介绍了一个在空间和尺度上全活跃特征交互(fully active feature interaction across both space and scales)的特征金字塔transf ...
- Docker安装系列教程
首先准备一台Centos7版本的虚拟机,它支持docker容器技术.本案例使用centos7虚拟机安装docker容器. 一.安装 1.启动虚拟机,配置虚拟机能够访问互联网 2. 安装支持软件包,提供 ...
- java类的主动使用/被动使用
对类的使用方式分为:主动使用.被动使用 所有的java虚拟机实现必须在每个类或接口被java程序"首次主动使用"时才初始化他们 ps:被动使用不会初始化类,但是有可能会加载类(JV ...
- Servlet3.0提供的@WebServlet注解引用参数详情介绍
Servlet3.0提供的@WebServlet注解: servlet3.0所提供的@webservlet注解,用来将某个类注解为一个servlet类,简化了web.xml上的servlet配置, @ ...
- 9条消除if...else的锦囊妙计,助你写出更优雅的代码
前言 最近在做代码重构,发现了很多代码的烂味道.其他的不多说,今天主要说说那些又臭又长的if...else要如何重构. 在介绍更更优雅的编程之前,让我们一起回顾一下,不好的if...else代码 一. ...
- 使用postman添加cookie失败和cookie消失问题
例如 groupId=2; path=/; domain=.www.baidu.com; HttpOnly; Expires=Tue, 16 Jul 2019 03:42:12 GMT; 添加失败和c ...
- spark:distinct算子实现原理
distinct的底层使用reducebykey巧妙实现去重逻辑 //使用reduceByKey或者groupbykey的shuffle去重思想rdd.map(key=>(key,null)). ...
- JavaScript正则表达式详解
在JavaScript中,正则表达式由RegExp对象表示.RegExp对象呢,又可以通过直接量和构造函数RegExp两种方式创建,分别如下: //直接量 var re = /pattern/[g | ...
- oracle坚决不挂2(SQLPLUS基础命令)
继续复习!!SQLplus基础命令,其实这个应该是第一个要复习的.因为基础,你懂得..要想学会跑,你先得知道该怎么走吧. win+R 输入cmd ,我们开始启动SQLplus sqlplus user ...