区间dp
区间dp
一、基本思路:分治思想,将一个序列问题不断往下分,分成满足问题条件的最小区间(长度不一定为1《其实是基本不为1》,2、3比较常见),然后解决问题。
二、dp细节:枚举i,j,k,(i是区间长度,j是起点,k不一定有)
三、基本代码框架:
(1)dfs思路:
dp[][]存需要使用的数值,要初始化成memset(dp,-1,sizeof(dp));
a[]存输入值
int dfs(int st, int ed)//输入区间两端点
{
if(st + a >= ed) return 0;
if(dp[st][ed] != -1 ) return dp[st][ed];
dp[st][ed] = 1e9;
for(int i = 1;i <= n;i++)
{
dp[st][ed] = (min/max){dp[st][ed], dp[st][i] + dp[i][ed] + 实际情况需要的计算(常用a[])};
}
}
主函数:dfs(1,n); 结果:dp[1][n];
注意:在dp状态转移方程部分,dp[i][ed] 中i可以按照实际问题要求改成i+1(通常是一个区间端点左右的数还需在处理该区间内部数据时用到)
}
(2)dp做法
int maxa = 1e9;
dp[][]存答案
for(int i = 2;i <= n - 1;i++)//i表示长度,j表示起点,k表示当前区间内最后删除的一个点 (闭区间)
{
for(int j = 1;i + j <= n;j++)
{
dp[j][i + j] = maxa;
for(int k = j + 1;k < i + j;k++)
{
dp[j][i + j] = min(dp[j][i+j],dp[j][k] + dp[k][i + j] + a[j] * a[k] * a[i + j]);//此时已经处理完了该区间内的所有的数据,故可看作只有最后一个删掉的数,和区间两端的数参与最后一次运算
}//把两个区间拼起来 (k是分界点)
}
}
四、例题(持续补充)
(1)乘法游戏
输入n个数,每次删去一个数,则得分 = 删去的数 * 前一个数 * 后一个数,头尾的数不可删去,要求将所有数都删去,求得分最小值
样例:
输入: 5
3 2 1 7 9
输出: 96
思路:我们很容易想到如果想求整体数列的最小值,就一定要让每一个小部分的值最小(如果连这个小部分的值都可以更小,那么整体值一定可以更小)。
所以我们可以先把整个数列分成很多个小区间,然后求每个区间所能达到的最优情况,再把区间合起来,最终得到的值一定是最小的。
(2)涂色(洛谷p4170)
假设你有一条长度为 55 的木版,初始时没有涂过任何颜色。你希望把它的 55 个单位长度分别涂上红、绿、蓝、绿、红色,用一个长度为 55 的字符串表示这个目标:RGBGR
。
每次你可以把一段连续的木版涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。例如第一次把木版涂成 RRRRR
,第二次涂成 RGGGR
,第三次涂成 RGBGR
,达到目标。
用尽量少的涂色次数达到目标。
样例:AAAAA 输出:1
样例:RGBGR 输出:3
思路:我们可以用一种神奇的思路解决问题,就是最开始一个位置操作一次,则ans = n,然后再判断哪些位置可以一次涂完。
可以发现在一个小区间内,如aba,因为两侧都有相同字符,即区间端点可以一次解决,则ans = (1 + 1 + 1) - 1;
所以,每次发现区间的端点相等,则把该区间的ans减一;
这种情况当然把整个序列分成无数个小序列(最短序列每个长度为1),再进行上面的操作,就能得出正确答案。