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
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
P3146 [USACO16OPEN]248 G
题目描述
(一个因为翻译而WA的“毒瘤”题)
给定一个长度为\(n\)的区间,在区间内相邻的且数字大小相同的两个数字可以合并的到一个比它\(+1\)数字
询问可以合并成的最大数值为多少
思路分析
-
一个线性区间dp,我们依旧是在区间内做处理
-
在一个区间内,枚举长度,并在这个区间内找一个分割点,是这个点两边的数值是相等的,然后进行大小比较
状态转移方程设计
-
我们可以设\(f[i][j]\)为区间\([i,j]\)内的合并出来的最大值
-
由此可以得到状态转移方程(状态可以根据需要灵活变化,此方程取\(j\)为分界点)
代码实现
#include
#include
#include
#include
#include
#include
P4170 [CQOI2007]涂色
题目描述
- 一开始给你一个空序列,可以使一个字母连续覆盖相邻的任意等长的区间,求最少有几次可以得到目标状态
思路分析
- 因为是字符串,输入的时候处理一下让他的编号从\(1\)开始,好进行处理
- 首先,每个长度为\(1\)的区间都赋值为\(1\),因为他需要进行一次涂色
- 其次,我们可以发现的是,当我们枚举一个区间时,如果左右端点是一样的,那么我们可以对左右端点分别做操作,比较一下左端点右移\(1\)的区间去覆盖左端点次数小还是右端点左移动\(1\)的区间去覆盖右端点所使用的次数少。
- 最后进行正常的区间断点枚举,知道找出最小的方案为止
状态转移方程设计
- 正常的状态转移方程,设\(f[i][j]\)为区间\([i,j]\)变成最终状态所需要的最小次数
- 那么可以得到状态转移方程:
代码实现
#include
#include
#include
#include
#include
P4290 [HAOI2008]玩具取名
思路分析
- 因为给的每一个转化的字符串是两个值,所以我们只需要通过枚举分别都可以被一个字母表示的两个小区间,然后看一下这两个字母是否可以被一个字母来代替,也就是找一找是否可以用一个字符来代替整个区间
状态转移方程设计
- \(f[i][j][k]\)表示区间\([i,j]\)可以通过\(k\)转化过来
- \(can[i][j][k]\)表示\(i,j\)可以通过\(k\)转化过来(\(z1+z2->z\))
代码实现
#include
#include
#include
#include
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
状压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
#include
#include
#include
#include
#include