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] ...
随机推荐
- Linux嵌入式学习-交叉编译mplayer
http://bbs.gkong.com/archive.aspx?ID=286721
- [php]配置文件中的超时时间
概要 php.ini l max_execution_time l max_input_time php-fpm.conf l process_control_timeout l reques ...
- 前端Vscode常用插件概述
以下是我自己在工作中常用的插件,写给刚入门的前端coder.VSCode插件商店中实用的插件还是很多的,大家也可以对感兴趣的插件下载下来尝试一下的! 持续更新 插件名称 概述 作用 常用默认快捷键 C ...
- chrome实现网页高清截屏(F12、shift+ctrl+p、capture)
打开需要载屏的网页,在键盘上按下F12,出现以下界面 上图圈出的部分有可能会出现在浏览器下方,这并没有关系.此时按下 Ctrl + Shift + P(Mac 为 ⌘Command +⇧Shift + ...
- 定期删除文件夹中的文件——C#
下面是自定义的一个函数,参数分别为:文件夹名称.文件后缀.保存天数 逻辑是获取当前系统的时间,和文件创建时间去作差,如果结果大于保存天数,就删除它 /// <summary> /// 定期 ...
- java操作hive和beeline的使用
一.java操作hive 1.启动服务:hiveserver2,让hive开启与外部连接的服务 nohup hiveserver2 1>/dev/null 2>/dev/null & ...
- .NET 云原生架构师训练营(模块二 基础巩固 RabbitMQ 工作队列和交换机)--学习笔记
2.6.4 RabbitMQ -- 工作队列和交换机 WorkQueue Publish/Subscribe Routing EmitLog WorkQueue WorkQueue:https://w ...
- PHP将数据集转换成树状结构
/** * 把返回的数据集转换成Tree * @param array $list 要转换的数据集 * @param string $pid parent标记字段 * @param string $l ...
- 值得推荐的C#不同版本语言特性
C#语言在不断地版本升级中,为我们提供了很多新的语言特性.其中,有很多使用的语言特性,我觉得在实际开发中用起来很方便,能够简化我们的代码,增强可读性,提高开发效率. 小编不才,在这里给大家整理了一些实 ...
- 白日梦的Elasticsearch笔记(一)基础篇
目录 一.导读 1.1.认识ES 1.2.安装.启动ES.Kibana.IK分词器 二.核心概念 2.1.Near Realtime (NRT) 2.2.Cluster 2.3.Node 2.4.In ...