区间DP

来源:互联网 发布:无人机自动降落算法 编辑:程序博客网 时间:2024/06/05 19:09

区间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 1000000000using 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 1000000000using 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];



  

原创粉丝点击