zoukankan      html  css  js  c++  java
  • 区间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必考,所以要花大功夫在这上面。

  • 相关阅读:
    CentOS虚拟机和物理机共享文件夹实现
    集训第六周 数学概念与方法 概率 数论 最大公约数 G题
    集训第六周 数学概念与方法 概率 F题
    集训第六周 E题
    集训第六周 古典概型 期望 D题 Discovering Gold 期望
    集训第六周 古典概型 期望 C题
    集训第六周 数学概念与方法 UVA 11181 条件概率
    集训第六周 数学概念与方法 UVA 11722 几何概型
    DAG模型(矩形嵌套)
    集训第五周 动态规划 K题 背包
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/12548780.html
Copyright © 2011-2022 走看看