您的位置:首页 > 其它

区间dp状态的考虑与临界的分析

2018-02-19 15:25 218 查看
最近在写一些关于dp的题,春节左右搞的时间也不算多(肝空境复刻了- -),区间dp在刚看比较经典的堆石子的时候还能理解,但是加深难度后就比较迷了,尤其头铁在区间的状态划分上。让几个题搞的分不清区间开始于末尾的2个元素到底包括不包括。总而言之还是对于区间dp的状态的定义很模糊。此篇博文会写一下我个人对于区间dp的理解以及心得。

什么是区间dp

之前看过一些视频,感觉讲的不是特别好,我个人理解为:从一个线性的数据中根据花费合并区间,合并的时候分为左区间、右区间、分界点。左区间与右区间是已经完成的区间,直接调用值即可。根据题意处理分界点,然后将左右区间和值合并,组成新的区间。这样,我们可以将一段区间[i,j]定义为一个状态,枚举分界点i<k<j(具体是否等于i或者j要看具体情况),处理整个区间。最终dp[begin][end]的值就是最终结果。
总的来说,先求出小区间的值,然后慢慢合并为一个大的区间。最终合并为需要求的区间。由此,我们可以大概猜出来一般的区间dp的时间复杂度是O(n^3)。三重for循环分别循环:
1.区间长度,由小到大。
2.区间起点,由小到大
3.枚举区间中的分界点,区分左右区间。
我看的很多博客都是这样写的,但是我个人的写法不太一样,有点类似背包的循环写法:
1.区间起点,由大到小,这样保证处理的子区间的值一定存在。
2.区间终点,由起点开始枚举,直到大区间尾。
3.枚举区间的分界点,区分左右区间。
这2种方法是一样的,哪一种都可以。当然下面的内容都以我个人的写法为主。

区间dp的状态定义和边界值

其实说一下真的很简单:dp[i][j]表示区间i到j的状态。但是实际上我们需要考虑这个dp[i][j]是否包括i与j(博主个人经常迷在这个位置,还是太弱了- -)。以hdoj-5115与poj-1651为例,博主单独写了hdoj-5115的题解,可以移步:
hdoj-5115

hdoj-5115,也是14年ICPC北京站的第四题,题意大概是一排狼可以组成阵地buff,每个狼有单独的攻击值,左右两侧的狼有一个ex攻击值,可以给左右两侧的狼加buff,累加攻击力。我们设定2个数组wof[MAX]、wof_ex[MAX]分别表示每只狼的2个攻击值,那么第二只狼的实际攻击力是:wof[2]+wof_ex[1]+wof_ex[3]。其实就能转化成区间dp的问题了。这时我们定义状态dp[i][j]为杀第i只狼到第j只狼,这里包括第i只与第j只。那么dp[i][i]表示我杀第i只狼到第i只狼,就是只杀一只。这种情况是存在的。那么在枚举k的时候,k
4000
可以等于i或者j,因为左右区间的一个是单独一只狼的状态是有定义的。那么核心算法就是这个:for (int i = N; i > 0; i--) {
    for (int j = i; j <= N; j++) {
        dp[i][j] = INF;
        for (int k = i; k <= j; k++) {
            int heat = wolves[k] + wol_ex[i - 1] + wol_ex[j + 1];
            dp[i][j] = Min(dp[i][j], dp[i][k - 1] + dp[k + 1][j] + heat);
        }
    }
}区间i值最大是n,所以j的起始值是i。因为dp[i][i]表示就杀第i号的狼。k在枚举的时候i与j都能取。那么k是中间值,可以知道左区间是dp[i][k-1],右区间是dp[k+1][j],注意要单独留k,因为k不能包括在区间内,k是没有被杀的狼,需要单独计算伤害。由于左右区间都已经计算完成了,或者说区间内的狼都已经被杀,那么杀k狼的时候,k狼左边的狼是i-1号的狼(如果有的话),i狼已经被杀了,同理右狼是j+1号。所以杀k狼的值应该是wolves[k] + wol_ex[i - 1] + wol_ex[j + 1]。整体简单粗暴,下面的题与该题有异曲同工之妙。
poj-1651,据说区间dp入门经典题。题意大概是玩牌,线性排列的牌,最左边与最右边的牌不能拿,只能拿中间的牌。拿一张牌,得分是该牌*左边一张牌*右边一张牌。假设用数组num储存牌的值。抽第i牌的得分就是:num[i]*num[i-1]*num[i+1]。这里由于左边与右边的牌是不能被算的,所以博主刚开始也迷了很长时间,dp[i][j]中第i与第j张牌到底算不算分?其实如果考虑算不算i、j就已经把这个题想麻烦了。跟杀狼的一样,我们设定dp[i][j]就是拿第i张到第j张牌,但是i与j的范围均为(1,n)。也就是说,不算头尾2张牌,我们可以这样理解,第1张牌放在num[0]的位置,第n张牌放在num[n-1]的位置,剩下的n-2张牌放在1到n-2的位置上。那么就跟杀狼一样了,最终拿完所有的牌,头尾2张牌就是跟杀狼的ex数组一样,最终就取个值而已。那么可以得到与上面很像的算法:for(int i = n-1;i > 1;i--){
for(int j = i;j < n;j++){
dp[i][j] = INF;
for(int k = i;k <= j;k++){
int val = num[k]*num[i-1]*num[j+1];
dp[i][j] = Min(dp[i][j],dp[i][k-1]+dp[k+1][j]+val);
}
}
}

简单总结

所以说分区间的重点在于如何定义状态。简单的例如杀狼,虽然题可能刚开始没有太多思路,但是对于区间dp而言,实现上还是相对简单的,就是定义第i只狼杀到第j只狼。稍微难一点的例如抽牌,首尾2张牌不取的情况下会容易在考虑dp[i][j]中到底取不取i、j走入死胡同。其他的题也是一样,定义状态就是定义范围,包括2个范围的临界条件,也就是说dp[i][i]有定义,指就进行i位置的操作;dp[i][j]就表示总共有j-i种操作的方法,包括i与j。k循环的时候区分左右区间,不能包括k,因为k还没有操作。
简而言之:定义dp[i][j]一定包括i、j,dp[i][i]有定义,枚举k就是抽i到j所有的内容,包括i与j。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: