码迷,mamicode.com
首页 > 其他好文 > 详细

区间DP

时间:2020-07-04 01:04:24      阅读:41      评论:0      收藏:0      [点我收藏+]

标签:表示   前缀   scan   n+2   线性   lin   mes   元素   出现   

区间动态规划特点

区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来由很大的关系。令状态 \(f(i,j)\) 表示将下标位置 \(i\)\(j\) 的所有元素合并能获得的价值的最大值,那么 \(f(i,j)=max\{ f(i,k)+f(k+1,j)+cost \}\)\(cost\) 为将这两组元素合并起来的代价。 ——转自 OI Wiki

  简单来说,区间动归的一大特点就是,将多个部分整合(或者反过来)。求解的时候,可以将一段区间分成两部分来处理。由于先处理的是区间短的情况,所以就可以递推出完整区间。

状态转移方程

  考虑一条链的情况,用 \(dp[i][j]\) 来表示区间 \([i,j]\) 下的最优解(比如,最大值):

  • 为了先得到短区间的解,再从中递推出长区间,所以最外层循环应是区间长度的循环
    for (int len = 1; len <= n - 1; len++)
    
  • 随后遍历起点
    for (int l = 1; l + len <= n; l++)
    
  • 于是终点 \(r=l+len\) ,遍历 \(k(l+1\leqslant k\leqslant r)\)\(dp[l][r]\) 可以从 \(dp[l][k-1]+dp[k][r]+cost\) 中更新
    for (int k = l + 1; k <= r; k++)
      dp[l][r] = max(dp[l][r], dp[l][k - 1] + dp[k][r] + cost(l, r);
    
  • 由于是一条链,最长区间肯定是 \([1,n]\) ,输出 \(dp[1][n]\) 就好了

  而对于环形的区间DP题,区间不一定是 \([i,j](i\leqslant j)\) ,最后结果的最优解也不一定是 \([1,n]\) ,而可能是 \([3,2]\) ,这对我们的循环造成了麻烦。然鹅这样的区间都是由 \([j,n]\)\([1,i]\) 组成的,如果我们把环拆成链,再在 \(n\) 后接上 \(1\sim n\) 同样的一条链,就能解决问题。

原数据 1 2 3 …… n 1 2 3 …… n
存入数组 a[1] a[2] a[3] …… a[n] a[n+1] a[n+2] a[n+3] …… a[2n]

  区间 \([j,i](i\leqslant j)\) 等价于 \([j,n+i]\) ,区间正常了,随后的递推和链状DP是一样的。不过最后输出结果的时候还要注意一点,最优解不一定是 \([1,n]\) 了,也有可能是 \([2,n+1]\) 等等,需要用循环来跑一遍。
  核心代码

for (int len = 1; len <= n - 1; len++)
    for (int l = 1; l + len <= 2 * n; l++)
    {
        int r = l + len;
        for (int k = l + 1; k <= r; k++)
            dp[l][r] = max(dp[l][r], dp[l][k - 1] + dp[k][r] + cost(l, r));
    }
int ans = 0;
for (int i = 1; i <= n; i++)
    ans = max(ans, dp[i][i + n - 1]);

典型例题

链式区间DP

  看来对一条链的区间DP过于简单,连道题目都没找到(逃) 打脸了,找一道看吧:P3146 【USACO16OPEN】248 G

#include <bits/stdc++.h>
using namespace std;

int n, ans = 0, dp[250][250];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &dp[i][i]);
        ans = max(ans, dp[i][i]);
    }
    for (int len = 1; len <= n - 1; len++)
        for (int l = 1; l + len <= n; l++)
        {
            int r = l + len;
            for (int k = l + 1; k <= r; k++)
                if (dp[l][k - 1] == dp[k][r] && dp[l][k - 1])
                    dp[l][r] = max(dp[l][r], dp[l][k - 1] + 1);
            ans = max(ans, dp[l][r]);
        }
    printf("%d\n", ans);
    return 0;
}

环式区间DP

  P1880 【NOI1995】石子合并

#include <bits/stdc++.h>
using namespace std;

int n, a[205], sum[205] = {0}, dp_min[205][205] = {0}, dp_max[205][205] = {0};

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", a + i);
        a[n + i] = a[i];
    }
    for (int i = 1; i <= 2 * n; i++)
        sum[i] = sum[i - 1] + a[i]; //前缀和为 cost 函数做好铺垫
    for (int len = 1; len <= n - 1; len++)
        for (int l = 1; l + len <= 2 * n; l++)
        {
            int r = l + len;
            dp_min[l][r] = __INT_MAX__;
            for (int k = l + 1; k <= r; k++)
            {
                dp_min[l][r] = min(dp_min[l][r], dp_min[l][k - 1] + dp_min[k][r] + sum[r] - sum[l - 1]);
                dp_max[l][r] = max(dp_max[l][r], dp_max[l][k - 1] + dp_max[k][r] + sum[r] - sum[l - 1]);
            }
        }
    int ans_min = __INT_MAX__, ans_max = 0;
    for (int i = 1; i <= n; i++)
    {
        ans_min = min(ans_min, dp_min[i][i + n - 1]);
        ans_max = max(ans_max, dp_max[i][i + n - 1]);
    }
    printf("%d\n%d\n", ans_min, ans_max);
    return 0;
}

区间DP

标签:表示   前缀   scan   n+2   线性   lin   mes   元素   出现   

原文地址:https://www.cnblogs.com/guapiii/p/13233274.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!