• 区间DP 学习笔记


    前言:本人是个DP蒟蒻,一直以来都特别害怕DP,终于鼓起勇气做了几道DP题,发现也没想象中的那么难?(又要被DP大神吊打了呜呜呜。

    -----------------------

    首先,区间DP是什么?

    区间DP是一种以区间长度为阶段的DP方法。这种DP的解法较为固定,一般都是先枚举区间长度,再枚举左端点,根据左端点+长度推出右端点,然后枚举中间的断点进行转移。

    伪代码:

    for (int len=2;len<=n;len++)
        for (int i=1;i<=n-len+1;i++)
        {
            int j=i+len-1;
            for (int k=i;k<j;k++) f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
            ans=max(ans,f[i][j]);
        }    

    一句题外话:最短路算法中的佛洛依德算法的本质就是区间DP。

    --------------------------

    区间DP有两种形式(还是需要选手自己转化的。

    一.环型DP

    1.石子合并

    经典题目,每个OI初学者必做的一道题。

    首先我们要解决的是环的问题。我们可以将长度扩大到原来的二倍,破换成链。这是一种非常重要的思想,以后做题会经常遇到。

    然后我们考虑区间DP的问题。每个区间都是由子区间合并而来,代价是两个子区间之和。所以我们不妨枚举区间内的断点,看哪种合并方式能得到最优解。

    所以不难得出状态转移方程:

    $f1[i][j]=min(f1[i][j],f1[i][k]+f1[k+1][j])$

    $f2[i][j]=max(f2[i][j],f2[i][k]+f2[k+1][j])$

    初始化即为$f[i][i]=a[i]$。

    Code:

    #include<bits/stdc++.h>
    using namespace std;
    int f1[205][205],f2[205][205],s[205][205];
    int a[205],sum[205],n,ans1,ans2;
    void init()
    {
        cin>>n;
        for (int i=1;i<=n;i++){
            cin>>a[i];
            a[i+n]=a[i];
        }
        for (int i=1;i<=n*2;i++)
        {
            sum[i]=sum[i-1]+a[i];
            f2[i][i]=0;f1[i][i]=0;
        }
    }
    void dp()
    {
        for (int l=2;l<=n;l++)
            for (int i=1;i<=2*n-l+1;i++)
            {
                int j=i+l-1;
                f1[i][j]=0x7fffffff/2;f2[i][j]=0;
                for (int k=i;k<j;k++)
                {
                    f1[i][j]=min(f1[i][j],f1[i][k]+f1[k+1][j]);
                    f2[i][j]=max(f2[i][j],f2[i][k]+f2[k+1][j]);
                }
                f1[i][j]+=sum[j]-sum[i-1];
                f2[i][j]+=sum[j]-sum[i-1];
            }
        ans1=0x7fffffff/2;ans2=0;
        for (int i=1;i<=n;i++) ans1=min(ans1,f1[i][i+n-1]);
        for (int i=1;i<=n;i++) ans2=max(ans2,f2[i][i+n-1]);
    }
    int main()
    {
        init();
        dp();
        cout<<ans1<<endl<<ans2<<endl;
        return 0;
     } 

    多边形

    这也是一道环型DP,而且细节蛮多的,有兴趣不妨可以到我的博客里看一看。链接已备好。

    二.链型DP

    有些题太过于直白导致一眼看出状态转移方程,这里就不写了。直接上一道比较有难度的题。

    关路灯

    根据题中的提示,我们发现区间$[i,j]$的转移有两种情况:

    1.直接顺着走下来。

    2.走到某处折返。

    又因为老张只能关他相邻的灯,所以我们得出状态转移方程:

    $f[i][j][0]=min(f[i+1][j][0]+(pos[i+1]-pos[i])*(sum[n]-sum[j]+sum[i]),f[i+1][j][1]+(pos[j]-pos[i])*(sum[n]-sum[j]+sum[i]))$
    $f[i][j][1]=min(f[i][j-1][1]+(pos[j]-pos[j-1])*(sum[i-1]+sum[n]-sum[j-1]),f[i][j-1][0]+(pos[j]-pos[i])*(sum[i-1]+sum[n]-sum[j-1]))$

    其中前缀和要预处理,$0$表示在左端点,$1$表示在右端点。

    Code:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=55;
    int f[maxn][maxn][2],n,c,pos[maxn],w[maxn],sum[maxn];
    int main()
    {
        scanf("%d%d",&n,&c);
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++) f[i][j][0]=f[i][j][1]=0x3f3f3f3f;
        for (int i=1;i<=n;i++) scanf("%d%d",&pos[i],&w[i]),sum[i]=w[i]+sum[i-1];
        f[c][c][0]=f[c][c][1]=0;
        for (int len=2;len<=n;len++)
            for (int i=1;i<=n-len+1;i++)
            {
                int j=i+len-1;
                f[i][j][0]=min(f[i+1][j][0]+(pos[i+1]-pos[i])*(sum[n]-sum[j]+sum[i]),f[i+1][j][1]+(pos[j]-pos[i])*(sum[n]-sum[j]+sum[i]));
                f[i][j][1]=min(f[i][j-1][1]+(pos[j]-pos[j-1])*(sum[i-1]+sum[n]-sum[j-1]),f[i][j-1][0]+(pos[j]-pos[i])*(sum[i-1]+sum[n]-sum[j-1]));
            }
        printf("%d",min(f[1][n][0],f[1][n][1]));
        return 0;
    } 

    后记:其实DP题目量还是比较大的,而且NOIp必考,所以要花大功夫在这上面。

  • 相关阅读:
    Link assemblies causes app crashes if you have an EditText
    Link causes xamarin Android binding library project to crash
    Linux系统目录结构详解
    Centos Linux系统优化二
    Centos Linux系统优化一
    rm命令详解
    mv命令详解
    cp命令详解
    echo命令详解
    touch命令详解
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/12548780.html
Copyright © 2020-2023  润新知