区间DP小结

也写了好几天的区间DP了,这里稍微总结一下(感觉还是不怎么会啊!)。

但是多多少少也有了点感悟:

一、在有了一点思路之后,一定要先确定好dp数组的含义,不要模糊不清地就去写状态转移方程。

二、还么想好。。。想到了再加上去.。。。

之前也看了别人的总结,也给出了不少区间DP的模板。这里我也贴一下基本的模板:

 1 区间DP模板
 2 for (int len = 1; len < n; len++) { //操作区间的长度
 3         for (int i = 1; i+len <= n; i++) { //始末
 4         int j=i+len;
 5             //检查是否匹配(非必须)
 6             for (int k = i; k < j; k++) {
 7                 //枚举区间分割
 8             }
 9             //检查是否匹配(非必须)
10         }
11 }

这是第一类的区间DP模板,这类区间DP通常是枚举区间成为若干块子区间的合并,从而获得最优解。

第一类区间DP:

NYOJ 石子合并(一)(区间DP)

题目大意:

 有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

解题思路:

设dp[i][j]为合并完[i,j]区间所有石子的最小花费,sum[i]是1~i对石子价值的前缀和。

得到状态转移方程:dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i]+sum[i]-sum[j-1]),(i=<k<j)

POJ 2955 Brackets(括号匹配一)

题目大意:给你一串字符串,求最大的括号匹配数。

解题思路:

设dp[i][j]是[i,j]的最大括号匹配对数。

则得到状态转移方程:

if(str[i]=='('&&str[j]==')'||(str[i]=='['&&str[j]==']')){
  dp[i][j]=dp[i+1][j-1]+1;
}
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]) ,(i<=k<j)

POJ 1141 Brackets Sequence(括号匹配二)

题目大意:给你一串字符串,让你补全括号,要求补得括号最少,并输出补全后的结果。

解题思路:

设dp[i][j]为[i,j]最少需要补齐的括号数,得到状态转移方程:

if(str[i]=='('&&str[j]==')'||str[i]=='['&&str[j]==']'){
  dp[i][j]=dp[i+1][j-1];
}
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]),(i=<k<j)

然后,如果dp[i][j]由dp[i][k]和dp[i][k+1]组成,则记录path[i][j]=k,否则记为-1,最后递归输出一下就行了。

注意:输入用while(~scanf("%s",s))会错,不知道为什么要改成gets(s)

ZOJ 3537 Cake(凸包+区间DP)

题目大意:给出一些点表示多边形顶点的位置,如果不是凸多边形(凸包)则不能切,直接输出"I can't cut."
切多边形时每次只能在顶点和顶点间切,每切一次的花费为 cost(i, j) = |xi + xj| * |yi + yj| % p。
问把多边形切成最多个不相交三角形的最小代价是多少。

解题思路:先求出凸包,接着可以用区间DP解决,设dp[i][j]为以i为起点,j为终点的凸包被切成三角形的最小花费。
那么可以得到状态转移方程:dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+cost[i][k]+cost[k][j])。

POJ 1651 Multiplication Puzzle(区间DP)

题目大意:
给你n个数,可以取[2,n-1]的数,若取a[i]则花费是a[i]与和a[i]相邻两数的乘积,比如有5个数字10 1 50 20 5,
玩家可以依次拿走1、20、50,总花费10 * 1 * 50 + 50 * 20 * 5 + 10 * 50 * 5 = 500 + 5000 + 2500 = 8000。
依次拿走50、20、1,总花费1*50*20 + 1*20*5 + 10*1*5 = 1000+100+50 = 1150。
求最小的总花费。
解题思路:
dp[i][j]表示取完[i+1,j-1]内的数的最小花费。
于是得到状态转移方程:dp[i][j]=min(dp[i][j],dp[i][k+1]+dp[k+1][j]+a[i]*a[k+1]*a[j]),(i<k+1<j)

HDU 5115 Dire Wolf (区间DP)

题目大意:
有一些狼,从左到右排列,每只狼有一个伤害A,还有一个伤害B。杀死一只狼的时候,会受到这只狼的伤害A和这只狼两边的狼的伤害B的和。
若两只狼之间的狼都被杀了,这两只狼也算相邻。求杀掉一排狼的最小代价。
解题思路:
设dp[i][j]为消灭编号从i到j只狼的代价,那么结果就是dp[1][n]
枚举k作为最后一只被杀死的狼,此时会受到a[k]和b[i-1] b[j+1]的伤害 取最小的即可。
可列出转移方程:

dp[i][j]=min(dp[i][j], dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1])
dp[i][i]=a[i]+b[i-1]+b[j+1];

LightOJ 1422 Halloween Costumes(区间DP)

题目大意:去参加派对,有n场派对,每场派对要穿第ai种衣服,可以选择外面套一件,也可以选择脱掉。问至少需要穿多少次衣服。

解题思路:

初始化dp[i][i]=1,表示一场派穿一件衣服,设dp[i][j]表示i~j最少需要穿几次衣服,

得到状态转移方程:

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])(i<=k<j)

if(a[i]==a[j]){

   dp[i][j]--;

}

若a[i]==a[j]那么代表在起点穿该件衣服,终点可以脱掉之前的衣服用之前的衣服。所以答案需要-1。

HDU 2476 String painter(区间DP+思维)

题目大意:给你字符串A、B,每次操作可以将一段区间刷成任意字符,问最少需要几次操作可以使得字符串A等于B。
解题思路:

先计算出将空串刷成字符串B的最小操作数,再去计算将A串刷成B串的最小操作数。

设dp[i][j]表示将空串[i,j]刷成跟B串一样的最小操作次数,所以得到状态转移方程:

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]),(i<=k<j)
if(_str[i]==_str[j])
  dp[i][j]--;

这里跟LightOJ 1422 写法差不多。

接下来设ans[i]表示将字符串A的[0,i]刷成B的最小操作次数,得到状态转移方程:

ans[i]=min(ans[i],ans[j]+dp[j+1][i]),(0<=j<i)

 

HDU 4283 You Are the One(区间DP(最优出栈顺序))

题目大意:
有一群屌丝,每个屌丝有个屌丝值,如果他第K个上场,屌丝值就为a[i]*(k-1),通过一个小黑屋(可以认为是栈)来调整,使得最后总屌丝值最小。
解题思路:
题目可以理解为给你一个栈,然后让你安排出栈入栈顺序,使得总的屌丝值最小,区间DP,设dp[i][j]为使区间[i,j]屌丝全部上阵的最小屌丝值之和,
不考虑[i,j]外的花费,sum[i]为1~i的屌丝值前缀和。
于是得到状态转移方程:dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]+d[i]*(k-i)+(sum[j]-sum[k])*(k-i+1)}(i<=k<=j)

这题真的挺难的。。。

 

hihocoder1636 Pangu and Stones(区间DP(石子合并变形))

题目大意:有n堆石头,每次只能合并l~r堆,每次合并的花费是要合并的石子的重量,问你合并n堆石子的最小花费,若不能合并则输出0。

解题思路:

这算是石子合并的加强版了吧,原来石子合并是只能两堆两堆地合并,而现在对合并堆数加了一个范围[l,r]。这题我看到的时候也没什么想法,看了题解才会的,而且也看的不是特别懂。

首先定义数组dp[i][j][k]表示将[i,j]的石子合并成k堆需要的最小花费。

那么我们可以得到状态转移方程:

①p>1时,dp[i][j][p]=min(dp[i][j][p],dp[i][k][p-1]+dp[k+1][j][1]),(i=<k<j,2=<p<=r)

②p==1时,dp[i][j][1]=min(dp[i][j][1],dp[i][j][k]+sum[j]-sum[i-1]),(l=<k<=r)

这题题解都看了好久,主要是不能理解状态转移方程的正确性。。。

 

 

接下来说一下第二类区间DP,虽然也跟区间有关,但是大的区间通常是从相邻子区间推得,如dp[i][j]通常会由dp[i][j-1]、dp[i+1][j]、dp[i-1][j+1]推得。

一般情况下的模板:

1 for(int len=1;len<n;len++){
2     for(int i=1;i+len<=n;i++){
3         int j=i+len;
4         //一些判断(非必须)
5         dp。。。。
6         //一些判断非必须
7     }
8 }

POJ 3280 Cheapest Palindrome(区间DP求改成回文串的最小花费)

题目大意:给你一个字符串,你可以删除或者增加任意字符,对应有相应的花费,让你通过这些操作使得字符串变为回文串,求最小花费。
解题思路:
比较简单的区间DP,令dp[i][j]表示使[i,j]回文的最小花费。
则得到状态转移方程:
dp[i][j]=min(dp[i][j],min(add[str[i]-'a'],del[str[i]-'a'])+dp[i+1][j]);
dp[i][j]=min(dp[i][j],min(add[str[j]-'a'],del[str[j]-'a'])+dp[i][j-1]);
if(str[i]==str[j])
dp[i][j]=min(dp[i][j],dp[i+1][j-1])

 

HDU 4632 Palindrome subsequence(区间DP求回文子序列数)

题目大意:给你若干个字符串,回答每个字符串有多少个回文子序列(可以不连续的子串)。
解题思路:

设dp[i][j]为[i,j]的回文子序列数,那么得到状态转移方程:
dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+MOD)%MOD
if(str[i]==str[j])
  dp[i][j]+=dp[i-1][j+1]+1

ZOJ 3469 Food Delivery(区间DP好题)

题目大意:
在x轴上有n个客人,每个客人每分钟增加的愤怒值不同。给出客人和餐厅的位置,以及客人每分钟增加的愤怒值,
和送餐行走一公里需要的时间,问送完n个客人的外卖最小愤怒值。
解题思路:
如果要访问完[i,j],那么它的子区间一定访问完了。
用dp[i][j][0]表示访问完区间[i,j]并留在左端点,dp[i][j][1]表示访问完区间[i,j]并留在右端点。
把饭店那个地方也加进去作为点。从饭店那个点往两边进行DP;
dp[i][j][0] 可以根据dp[i+1][j][0]和dp[i+1][j][1]得到。
dp[i][j][1] 可以根据dp[i][j-1][0]和dp[i][j-1][1]得到。

这题也挺难的。。。

 

HDU 4597 Play Game(区间DP(记忆化搜索))

题目大意:

有两行卡片,每个卡片都有各自的权值。

两个人轮流取卡片,每次只能从任一行的左端或右端取卡片。

假设两人都足够聪明,求先手能够取到的最大权值之和。

解题思路:

这题就归为区间DP吧,设dp[l1][r1][l2][r2]表示取完第一行[l1,r1]和第二行[l2,r2]的卡片时,先手能够获得的最大权值。

那么跟(l1,r1,l2,r2)关联的区间只有四个:

(l1+1,r1,l2,r2)

(l1,r1-1,l2,r2)

(l1,r1,l2+1,r2)

(l1,r1,l2,r2-1)

那么状态转移方程就为:

dp[l1][r1][l2][r2]=max(dp[l1][r1][l2][r2],sum-solve(l1+1,r1,l2,r2)),(l1<=r1)
dp[l1][r1][l2][r2]=max(dp[l1][r1][l2][r2],sum-solve(l1,r1-1,l2,r2)),(l1<=r1)

dp[l1][r1][l2][r2]=max(dp[l1][r1][l2][r2],sum-solve(l1,r1,l2+1,r2)),(l2<=r2)
dp[l1][r1][l2][r2]=max(dp[l1][r1][l2][r2],sum-solve(l1,r1,l2,r2-1)),(l2<=r2)

博弈类型的DP,挺有意思的,用记忆化搜索写起来比较方便。

 
原文地址:https://www.cnblogs.com/fu3638/p/8897795.html