zoukankan      html  css  js  c++  java
  • 区间DP的瞎扯淡


    写在前面
    连个引言都不加就直接开
    1. 区间DP状态常见模板:

    f[i][j]常常表示第i个到第j个这个区间内达到题目要求,所需要的最小值(最大值)

    如:

    1. [石子合并](https://www.luogu.com.cn/problem/P1880)
    这里的f[i][j]表示将i~j堆石头合并所需要的最小/大体力
    1. [关路灯](https://www.luogu.com.cn/problem/P1220)
    这里的f[i][j][0/1]表示将i~j的区间的灯完全关闭,老人站在左/右端点时剩下的灯的总功率
    1. [能量项链](https://www.luogu.com.cn/problem/P1063)
    这里的f[i][j]表示将i~j的珠子完全合并所能产生的最大能量

    #### 总结:对于一个区间DP的状态设计来说,常常以f[i][j]进行第i个到第j个这个区间的状态设计,并辅以第三维的变量来契合题目:
    如[乘积最大](https://www.luogu.com.cn/problem/P1018)的一种状态设计就是f[i][j][x]表示i~j的区间内使用x个乘号可以得到的最大值。
    1. 区间DP状态转移方程模板:

    f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+w(i,j));

    如:

    1. [石子合并](https://www.luogu.com.cn/problem/P1880)

    1 f[i][j]=max(f[i][k]+f[k+1][j]+w(i,j),f[i][j])
    2 
    3 =max(f[i][k]+f[k+1][j]+sum[j]-sum[i-1],f[i][j])
    4 
    5 又可以进一步优化得f[i][j]=max(f[i+1][j]]+sum[j]-sum[i-1],f[i][j-1]+sum[j]-sum[i-1]);

    但其本质仍然是原模板

    2.[又是关路灯](https://www.luogu.com.cn/problem/P1220)

    1 f[i][j][0]=
    2 min(f[i+1][j][0]+(a[i+1]-a[i])*(sum[i] +sum[n]-sum[j]),f[i+1][j][1]+(a[j]-a[i])*(sum[i]+sum[n]-sum[j]));
    3 
    4 f[i][j][1]=
    5 min(f[i][j-1][0]+(a[j]-a[i])*(sum[i-1]+sum[n]-sum[j-1]),f[i][j-1][1]+(a[j]-a[j-1])*(sum[i-1]+sum[n]-sum[j-1]));这里引用了[这篇z2415445508大佬的题解](https://www.luogu.com.cn/blog/user44468/solution-p1220)~~,原因是我懒得再推DP方程。~~


    实质上,利用本题条件,如石子合并一样将k优化为i+1或j-1同样是是相同模板变形。

    3.[能量项链我不想举例子](https://www.luogu.com.cn/problem/P1063)

    f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+a[i+1]*a[k+1]*a[j]);


    仅仅是w(i,j)稍有不同,这里更可以看出原模板变形。
    1. 边界模板

    边界是我们区间DP时常常漏掉的一环,因为它实在是太显而易见了( _~~然而并不是这样~~_ )

    以至于在我写这里的时候根本不知道该写什么

    通常来说,区间DP的边界是

    (1<=i,j<=n;)
    (i<=k<=j;)(=的添加与否由具体情况讨论,我见过最多的是(i<=k<j)和(i<k<=j)

    懒的举例子.


    4.前言总结

    所谓模板,只是用来在考试时帮助你认清题目本质的,一味套板是不可取的.



    # 正文

    1. 下定义!


    "区间动态规划是线性动归的拓展,在划分阶段时,往往是以区间的长度从小到大为阶段,逐步求解到到长度为N的区间的最优值,在枚举每一个区间的最优值时,由于当前区间内又有很多种合并方式并到到当前区间,那么就需要枚举这些合并方式中产生的值维护最优值,合并的不同,可以看作是区间划分的不同,划分时需要枚举划分的位置,即分割点。 那么对于区间类动态规划问题,往往可以将问题分解成为两两合并的形式。其解决方法是对整个问题设最优解,枚举分割点,维护最优值。
                                                                      ————[一位巨佬](https://www.cnblogs.com/SuYii/p/10988769.html)
    这段话还是说得非常精彩的,揭露了区间DP的本质——小区间递推大区间。

    2.实现
    ```cpp

    for(int l=1;l<n;l++)
    {
      for(int i=1,j;i+l<=2*n;i++)
      {
        j=i+l;
        f[i][j]=-0x7fffffff;
        fz[i][j]=0x7fffffff;
        for(int k=i;k<j;k++)
        {
          if(fu[k+1]==1)
          {
            f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
            fz[i][j]=min(fz[i][j],fz[i][k]+fz[k+1][j]);
          }
          if(fu[k+1]==2)
          {
              f[i][j]=max(f[i][j],f[i][k]*f[k+1][j]);
              f[i][j]=max(f[i][j],fz[i][k]*f[k+1][j]);
              f[i][j]=max(f[i][j],fz[i][k]*fz[k+1][j]);
              f[i][j]=max(f[i][j],f[i][k]*fz[k+1][j]);
              fz[i][j]=min(fz[i][j],fz[i][k]*fz[k+1][j]);
              fz[i][j]=min(fz[i][j],f[i][k]*fz[k+1][j]);
              fz[i][j]=min(fz[i][j],fz[i][k]*f[k+1][j]);
              fz[i][j]
    =min(fz[i][j],f[i][k]*f[k+1][j]); } } } }

    以上是[多边形](https://www.luogu.com.cn/problem/P4342)我的部分代码,其中:

    1. l代表一个区间的长度(这里我定义的并非标准的长度,而是图方便定义为(右端点-左端点)的值,标准的长度还要再加1;

    2. i代表左端点的位置;

    3. j代表左端点的位置;

    4. k代表划分的位置,即分割点。




    问:为什么要先枚举长度再枚举端点?
    答:区间DP是由小区间递推大区间的情况,若先端点再长度会出现大区间内有小区间的情况不明了的情况。

    如:f[1][3]理应=f[1][2]+f[2][3]+w(1,3);但此时f[2][3]并未求出


    3. 时间复杂度

    朴素的区间DP的时间复杂度为O(N^3)

    4. 破环成链

    [双是石子合并](https://www.luogu.com.cn/problem/P1880)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,a[5000],sum[5000],f[5000][5000],ans;
    inline void read(int &s) {
        int w=1;
        char ch=getchar();
        while(ch<'0'||ch>'9') {
            if(ch=='-')w=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
        s=s*w;
        return;
    }
    int main() {
        read(n);
        for(int i=1; i<=n; i++) {
            read(a[i]);
            sum[i]=sum[i-1]+a[i];
        }
        for(int i=n+1; i<=2*n; i++) {
            a[i]=a[i-n];
            sum[i]=sum[i-1]+a[i];
        }
        for(int l=1; l<n; l++) {
            for(int i=1,j; i+l<=2*n; i++) {
                j=i+l;
                f[i][j]=max(f[i+1][j]+sum[j]-sum[i-1],f[i][j-1]+sum[j]-sum[i-1]);
            }
        }
        for(int i=1; i<=n; i++) {
            ans=max(ans,f[i][i+n-1]);
        }
        printf("%d\n",ans);
        return 0;
    }
  • 相关阅读:
    python 数据类型 基础第二天
    Python基础第一篇
    前言、入门程序、常量、变量
    win10打开移动热点让手机连接上网教程
    win10移动热点问题
    博客园快速美化
    Idea提示没有符号类错误解决
    mybatis复习01
    test
    d190305面试题01总结
  • 原文地址:https://www.cnblogs.com/HKHbest/p/13479648.html
Copyright © 2011-2022 走看看