区间DP

区间DP

指一个序列上的问题,可以通过将区间分割更小的区间进行求解,在合并得到答案!

这类问题,有模板,但是题目的思维难度很大!切割多数从首尾切割,很多题目会迷惑你从中间开始,

通常模板:
    ·区间长度len:1->N
    ·区间首下标i:1->N-len+1
    ·区间[i,N-len+1]: 挑选(i,N-len+1)中每一个k将其化为[i,k]和[k+1,N-len+1]进行合并;

hdu 4283  You Are the One

题意:有一队人排好了队出场,每个人都有排队指数,对管理者的意见值=(出场序号-1)*排队指数,现在有一个栈,可以用来调整出场次序,可以将队列中首个人,安排入栈,第一个出场的人可以是栈首人,也可是队首人,问调整次序后意见值和最小值。

 

·题目太切合实际,有是一道新颖的实际应用题!

·题目非常容易陷入模拟其中情节过程,而越离正确的解题算法越远了!

  正确的模拟循序是:

      ·第一个人,他出场的次序可能为1N;而定下他的出场次序,我们就将问题切割成小的相似问题!如果他的次序为k,那么队列中第2到第k个人的次序为1k-1

        k+1个人到第N个人的出场次序为k+1N;剖分成两个子问题!

        这就是区间dp的标志!

      ·如果我们太关注那个栈,就会被吸引进找到第一个出场的人,进而陷入无法将问题切割成小的相似的子问题!

      ·编程步骤:·预处理好:1)区间长度为1的人的N种出场情况!

                              2)其它长度的F值全部为INF

                              3)为便于后面F的状态转移正确性:

                                 F[i+1][i]全部为0

                  ·DP过程:

                      1重循环:将len2遍历到N

                      2重循环:将 pos1遍历到N+1-len

                      3重循环:将区间首元素从1遍历到N+1-len

                      4重循环:状态转移过程:首元素i能够选的全部出场次序一

一比较每种的意见值,选出最小的。

      ·编程注意:1)不要直接复制题目给的输入数据,应该是字体不同,编译器Running error

2) 用来dpF数组不能每维长度刚刚开100,这里不行,报Runtime Error(ACCESS_VIOLATION)数组越界!

3) 时间复杂度上,虽然是4重循环,但是N最大为100;经过计算循环计算次数为N^4/12,N=100下,计算850万次;可支撑100多次N=100的case的测试,不用担心!提交花费时间:156ms

4) 编程时:可以不用做预处理,直接在计算到该F时,再在第四重前,

                            F[i+1][i]=0,F[i][last]=INF;

                      但我错误理解了F的含义,F[1][N][1]是我们的答案;脑袋抽了

一下,将F理解成了1到N个人,第1个人先出的最小意见

                      值,而F真实含义表示1到N个人中最先出场的人序号为1最

                      小意见值!所以导致后面做了一次for循环判断F[1][N][1…N]

之间的大小,其它F[1][N][2…N]不会与循环,都是0!答案是0!

/*
*HDU 4283---You Are the One
*/
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define INF 1000000000
using namespace std;
int F[110][110][110];
int D[110];
int main(){

    int T;scanf("%d",&T);
    int kase=0;
    while (T--){
        //cout<<T<<endl;
        int N;scanf("%d",&N);
        for(int i=1;i<=N;++i)
            scanf("%d",&D[i]);
        for(int i=1;i<=N;++i)
        for(int j=1;j<=N;++j){
            F[i][i][j]=(j-1)*D[i];
            F[i+1][i][j]=0;
            for(int k=i+1;k<=N;++k)
                F[i][k][j]=INF;
        }
        for(int len=1;len<=N;++len){
            for(int pos=1;pos<=N-len+1;++pos)
            for(int i=1;i+len-1<=N;++i){
                int last=i+len-1;
                for(int k=pos;k<=len+pos-1;++k){
                    int zj=i+k-pos;
                    F[i][last][pos]=min(F[i][last][pos],F[i+1][zj][pos]+F[zj+1][last][k+1]+(k-1)*D[i]);
                }
            }
        }
        int ans=INF;
        for(int i=1;i<=N;++i)
            ans=min(ans,F[1][N][i]);
        printf("Case #%d: %d\n",++kase,ans);
    }

    return 0;
}

Zoj 3469 Food Delivery

题意: 在x轴上一点X(坐标整数)有一家餐馆,x轴上有n个人,坐标不同(整数坐标)同 时定餐,餐厅仅一个送餐员,速度为1/V 米每秒,每个人等1min,都会增加对应

的不开心值,以后就不会定餐了,问不开心值sum最小值。

 

·这又是一道新颖的处理实际问题的应用题!

·这问题最先堵塞我的思维的是:我居然从餐厅点分析,就进行不下去了,无法切割成子问题!因为我无法从中部压缩区间为两段,这是不可能的;

·仔细分析(经过数月的经验积累):应该从两端分析,而恰恰最后一个送到的顾客存在于左右端点之中,我们可以假设最后一个送到的不为端点顾客,记为AA位于餐厅与端点B之中,那么送给B时,难道不经过A吗?所以矛盾!

       这是非常重要的结论!有了它,我们将问题归于首尾取石头问题,二种策略:

  取首或尾,将问题规模减一!已经有区间DP的影子了!

·但是还有一个问题需要处理:我们的DP数组F[star][end][pos]pos=0,头取;pos=1,尾取)代表的含义,开始我想它代表[star,end]最先送完且最后送starend(看pos)产生的不开心值sum,但通过举例和思维上的分析,这样存在问题:送完star在送star-1时,如果在[star,end]运送中,有可能会出现运送时间少,但sum大,而未被选中,但恰恰star-1的分钟不开心值很大,需要选择时间少的方案,那么出错了!

·处理这个问题,我们需要将思维在转一下,转移到从一个用户运送到另外一个用户的时间,恰好标志所有未收到货的人都此时等待这段时间!所以DP数组F[star][end][pos]

  标志最先送完这些人对所有人造成的不开心之和,所以没有时间的包袱了!

·状态转移方程:

    

F[i][last][0]=min(F[i+1][last][0]+(sum-B[i+1][last])*(p[i+1].x-p[i].x),

                     F[i+1][last][1]+(sum-B[i+1][last])*(p[last].x-p[i].x));

        ·从第last个人的位置去到第i个人的位置需要跑p[i+1].x-p[i].x,这段

          时间是除了第i+1到第last个外其他的人都必须等待的时间,所以选择

          这种到i的方案需要在原F[i+1][last][0]上在增加(sum-B[i+1][last])*(p[i+1].x-p[i].x)

        ·剩下的分析一样

         F[i][last][1]=min(F[i][last-1][0]+(sum-B[i][last-1])*(p[last].x-p[i].x),

                     F[i][last-1][1]+(sum-B[i][last-1])*(p[last].x-p[last-1].x));

·第i个人到last个人的单位不开心值的和B[i][last]可以在计算DP时,便带计算;因为它也是算出长度len-1的区间和,在基础上算len的区间和,并且此时因为缩减规模1,此时状态转移转移方程使用到的是长度为len的区间和;

·看题时:我非常不自信,硬是将1/V,结果wrong error 爆了!一定要吸取教训!

   

/*
*这道题隐藏的很深:
     实际上采取首尾分析的话,可以知道首尾中并有一个是当前全部中最后一个送到的;
     首尾取石头;
*740ms
*/
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>

using namespace std;
struct people{
    int  x;
    int b;
    bool operator <(const people &a){
        return x<a.x;
    }
}p[1010];

long long F[1010][1010][2];

//第三维为1:表示首为最后取
//第三维为0:表示尾为最后取
long long B[1010][1010];

int main(){

    int N,V,X;
    while(~scanf("%d%d%d",&N,&V,&X)){
        for(int i=1;i<=N;++i)
            scanf("%d%d",&p[i].x,&p[i].b);
        sort(p+1,p+N+1);
        long long sum=0;
        for(int i=1;i<=N;++i){
            B[i][i]=p[i].b;
            sum+=B[i][i];
        }
        for(int i=1;i<=N;++i)
            F[i][i][0]=F[i][i][1]=abs(p[i].x-X)*sum;


        for(int len=2;len<=N;++len)
            for(int i=1;i+len-1<=N;++i){
                int last=i+len-1;
                B[i][last]=B[i][last-1]+B[last][last];
                F[i][last][0]=min(F[i+1][last][0]+(sum-B[i+1][last])*(p[i+1].x-p[i].x),
                                  F[i+1][last][1]+(sum-B[i+1][last])*(p[last].x-p[i].x));
                F[i][last][1]=min(F[i][last-1][0]+(sum-B[i][last-1])*(p[last].x-p[i].x),
                                  F[i][last-1][1]+(sum-B[i][last-1])*(p[last].x-p[last-1].x));
        long long ans=min(F[1][N][0],F[1][N][1]);
        cout<<ans*V<<endl;
     }
    return 0;
}

Zoj 3537 Cake

题意:给你n个点,先看是否围成一个凸包,不能则输出: "I can't cut."

     能,则将凸包切成三角形三点均为凸包顶点的三角形(互相无重叠部分,三角剖分);

      除去凸包的边,新连边(点i,j相连)有一个cost(i,j)=|xi+xj|*|yi+yj|%p;求花费和最小值;

 

·这是一道综合题,需要判断是否为凸包,也利用凸包将区间DP进行包装!

·凸包点经过Javis March算法排成逆时针顺序后,因任意凸包的边都是切割后的一三角形的边,所以定下点1,n组成的边的切割三角形做最后得到的三角形!

类似poj1651线性删数问题,枚举区间除去端点i,last外,其它点看谁与端点组成三角形!记此点为k!

因为三角形不能区域覆盖,那么成功将区域分为三个[i,k]、[k,last]和三角形i|k|last;而即切了两下,cost[i][k]+cost[k][last](不用担心边ik为凸包的边,因为在划分时,将除边1n外所有凸包的边都做了花费计算,到最后在将凸包边的花费,减去就可以!)

·F[i][last]表示区间i到last组成区域切割成三角形的最小花费!

 取除端点外的任意点k:

  i、k、last组成三角形,进行比较,得到最小花费值!

      F[i][last]=min(F[i][last],F[i][k]+F[k][last]+cost[i][k]+cost[k][last]);

·编程中,用c语言输入时,输入case数量不知scanf前没加~或没有判断是否不等于-1

 EOF,所以输入不能终止!LTE!


#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <string.h>
#define Size 310
#define INF 1000000000
using namespace std;

int Left[Size];
struct Point
{
    int x;
    int y;
    bool extreme;
    Point(int xx=0,int yy=0,bool e=false):x(xx),y(yy),extreme(e){}

    friend Point operator-(const Point &a,const Point &b)
    {
        return Point(a.x-b.x,a.y-b.y,false);
    }
    friend double operator^(const Point &a,const Point &b)
    {
        return (a.x*b.y-a.y*b.x);
    }
}S[Size];

int cost[Size][Size];
int F[Size][Size];
int LTL(int n)
{
    int ltl=1;
    for(int i=2;i<=n;++i)
    {
        if(S[ltl].y>S[i].y||(S[ltl].y==S[i].y&&S[ltl].x>S[i].x))ltl=i;
    }
    return ltl;
}

int ToLeft(const Point &a,const Point &b,const Point &c)
{
    if(((b-a)^(c-a))>0)return 1;
    if(((b-a)^(c-a))<0)return 0;//c在向量a->b右侧
    return 2;//a,b,c共线
}
int tot;
void Jarvis(int n,int <l)
{
    ltl=LTL(n);
    int t=ltl;tot=0;
    do{
          Left[++tot]=t;
          int s=0;
          for(int k=1;k<=n;++k)
          {
              if(k!=t&&k!=s&&( s==0||( ToLeft(S[t],S[s],S[k])==0 ) ))
                s=k;
          }
          t=s;
      }while(t!=ltl);
}


int main(){

    int n,p;
    while(~scanf("%d%d",&n,&p)){
        for(int i=1;i<=n;++i){
            scanf("%d%d",&S[i].x,&S[i].y);
        }

        int ltl;
        Jarvis(n,ltl);
        if(tot!=n)printf("I can't cut.\n");
        else {
           for(int i=1;i<n;++i){
             F[i][i+1]=0;
             for(int j=i+1;j<=n;++j)
               cost[i][j]=(abs(S[Left[i]].x+S[Left[j]].x)*abs(S[Left[i]].y+S[Left[j]].y))%p;
           }

           for(int len=3;len<=n;++len)
             for(int i=1;i+len-1<=n;++i){
                 int last=i+len-1;
                 F[i][last]=INF;
                 for(int k=i+1;k<last;++k)
                    F[i][last]=min(F[i][last],F[i][k]+F[k][last]+cost[i][k]+cost[k][last]);
           }

           for(int i=1;i<n;++i)
             F[1][n]-=cost[i][i+1];
           printf("%d\n",F[1][n]);
        }
    }
    return 0;
}

 CodeForces 149D Coloring Brackets

题意:给你一个成功匹配的括号字符串(有‘(’、‘)’组成);现在给括号染色(只有两种颜色:红、蓝可染),每对匹配的括号中只能染一个,相邻括号不能染同色,

·写这道题,真心感觉它和其它区间DP不同,我花了很长一段时间,才真正意识上排除其它的入手方案,开始确定将F[t][i][j]的表达含义:

  ·i、j取0,1,2表示不取色、取红色和取蓝色

  ·i记录区间第first个括号染色i;

  ·j 记录区间第end个括号染色j;

  ·s[t]==‘(’,

  ·B[t]为此左括号匹配的右括号

·此最先表示[t,B[t]]之间括号染色后,前端为i色,后端为j色的方案数

 

·思路:

  ·我之所以认为与其它区间DP不同之处,就是这个len我们不好控制,用循环非常不好做,因为我们处理的是合法区间,根本无法处理非法区间,因为处理后我们无法处理其对应匹配的括号的染色情况;

  ·我们用递归来做,用合法区间段的组成定义来做,逻辑顺畅:

   

     ·如果处理合法区间已经为原子,不可在分,即区间长度为1,则

       一端染一种色,另一端不染色的4种情况均为1,其它为0;

          

        for(int i=1;i<3;++i){
            F[s][i][0]=F[s][0][i]=1;
        }

       例:()

     ·如果first对应的匹配括号刚好为end,即先求子问题[first+1,end-1]这个合法括号序列的所有情况。再求出[first,end]合法序列的所有情况;

         注:因为首尾匹配,所以除了一端染色和一端不染色的F,其它均为0;

            不妨以F[fisrt][i][0]为例,即可知它的染色情况数为全部F[first+1][j][k]且j≠i且k任意的和;

         

         dp(s+1,e-1);
         for(int i=1;i<3;++i){
            for(int j=0;j<3;++j)
            for(int k=0;k<3;++k){
                if(j!=i)F[s][i][0]=(F[s][i][0]+F[s+1][j][k])%mod;
                if(k!=i)F[s][0][i]=(F[s][0][i]+F[s+1][j][k])%mod;
            }
         }

       例:(()())

       ·如果first对应的匹配括号不为end,即存在多个合法序列并列;

     也合法,即先处理dp(first,B[first]),在处理dp(B[first]+1,end);

     顺利切割为两个子问题;类似上面过程,求出F[first][i][j];

     注意求dp(first,B[first]),计算得到的F[first][i][j]表示区间

     [first,B[first]]的情况数,下面我们要将F[first][i][j]迁移到表示区

     [first,end]的情况数;

     

     ·先用a[i][j]=0,来保留[first,end]的F[first][i][j]情况数,

       这是个合并过!将F[first][i][u]与F[B[first]+1][v][j],(任意u、v)只需要保证合法,即要么两者同为0(不染色)要么两者不相等;

 

   ·最终统计所有F[1][i][j]情况即为答案!

   ·编程时注意:递归中合并时,我判u、v颜色相同,非法而不能合并;

                 但不有考虑到u==v==0,表示不染色,也是合法,能合并;

                  (longlong)(F[s][i][u]*F[B[s]+1][v][j])%mod;

                 无效,F[s][i][u]与F[B[s]+1][v][j]乘积仍是int,所以等不到强制类型转换,我们应该最先将两者类型转换!

    

    dp(s,B[s]);
    dp(B[s]+1,e);
    int a[3][3];
    for(int i=0;i<3;++i)
     for(int j=0;j<3;++j){
        a[i][j]=0;
        for(int u=0;u<3;++u)
            for(int v=0;v<3;++v)
        {
            if(u!=v||(u==0&&v==0))a[i][j]+=(long long)(((long long)F[s][i][u])*((long long)F[B[s]+1][v][j]))%mod;
            a[i][j]%=mod;
        }
     }
     for(int i=0;i<3;++i)
        for(int j=0;j<3;++j)
         F[s][i][j]=a[i][j];



  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值