几个dp的陈年老题

Posted achenchen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了几个dp的陈年老题相关的知识,希望对你有一定的参考价值。

真 陈年老题

都是基础的dp优化

主要是展现我基础薄弱,菜得抠脚

 

1.四边形不等式

四边形不等式:w[i][j]+w[i+1][j+1]<=w[i+1][j]+w[j][i+1]

对于f[i][j]=f[x][k]+f[k+1][y]+w[x][y],若w同时满足区间包含单调性和四边形不等式,那么f也满足四边形不等式

若f满足四边形不等式,具有决策单调性。设f[i][j]的决策点k为s[i][j],s[i+1][j]>=s[i][j]>=s[i][j-1]

证明略。

最最经典的例题,合并石子

求最小值时因为满足四边形不等式,利用s的决策单调性即可n^2

求最大值时不满足四边形不等式,但是f[i][j]=max(f[i+1][j],f[i][j-1])+w[i][j] 也是n^2

llj给的证明(菜得连合并石子都不会的我):对于任意的四堆石子a,b,c,d不存在一种最优的合并方式是a,b合并,c,d合并再合并在一起。

因为从a到d依次合并和从d到a依次合并中一定有一个比他更优。列式可得。

技术分享图片
 1 //Achen
 2 #include<algorithm>
 3 #include<iostream>
 4 #include<cstring>
 5 #include<cstdlib>
 6 #include<vector>
 7 #include<cstdio>
 8 #include<queue>
 9 #include<cmath>
10 #include<set>
11 #include<map>
12 #define For(i,a,b) for(int i=(a);i<=(b);i++)
13 #define Rep(i,a,b) for(int i=(a);i>=(b);i--)
14 const int N=203;
15 typedef long long LL; 
16 typedef double db;
17 using namespace std;
18 int n,a[N],sum[N],f[N][N],g[N][N],w[N][N],s[N][N];
19 
20 template<typename T> void read(T &x) {
21     char ch=getchar(); x=0; T f=1;
22     while(ch!=-&&(ch<0||ch>9)) ch=getchar();
23     if(ch==-) f=-1,ch=getchar();
24     for(;ch>=0&&ch<=9;ch=getchar()) x=x*10+ch-0; x*=f;
25 }
26 
27 //#define DEBUG
28 int main() {
29 #ifdef DEBUG
30     freopen("1.in","r",stdin);
31     //freopen(".out","w",stdout);
32 #endif
33     read(n);
34     For(i,1,n) { read(a[i]); sum[i]=sum[i-1]+a[i]; }
35     For(i,1,n) { a[n+i]=a[i]; sum[n+i]=sum[n+i-1]+a[n+i]; }
36     n*=2;
37     For(i,1,n) For(j,1,n) w[i][j]=sum[j]-sum[i-1];
38     Rep(i,n,1) For(j,i+1,n) g[i][j]=max(g[i+1][j],g[i][j-1])+w[i][j];
39     memset(f,127/3,sizeof(f));
40     For(i,1,n) f[i][i]=0;
41     Rep(i,n,1) {
42         s[i][i]=i;
43         For(j,i+1,n) 
44             For(k,s[i][j-1],s[i+1][j]) { 
45                 if(k>=i&&k<j&&f[i][k]+f[k+1][j]+w[i][j]<f[i][j]) {
46                     f[i][j]=f[i][k]+f[k+1][j]+w[i][j];
47                     s[i][j]=k;
48                 }
49             }
50     }
51     n/=2;
52     int ans1=f[1][n],ans2=g[1][n];
53     For(i,1,n) ans1=min(ans1,f[i][i+n-1]),ans2=max(ans2,g[i][i+n-1]);
54     printf("%d\n%d\n",ans1,ans2);
55     return 0;
56 }
View Code

 

2.决策单调性和斜率优化

非常非常经典的例题,玩具装箱

列出dp方程:
$f_i=min_{j=0}^{j<i}  f[j]+(i-j-1-L+sum[i]-sum[j])^2$

1.
易证代价函数满足四边形不等式,或打表发现,具有决策单调性
维护单调队列,新进入一个决策点弹出队尾的一部分决策点然后在队尾二分即可。
更新答案的时候弹出队首的部分用完的决策点
注意决策点不一定入队。

2.

$x_j=sum_j+j$
$y_j=x_j^2+f_j$
$A_i=i+sum[i]-1-L$
$f_i=A_i^2-2*A_i*x_j+y_j$
若j优于k,则有
$-2*A_i*x_j+y_j<-2*A_i*x_k+y_k$

决策单调性,$j>k,x_j>x_k,y_j>y_k$

$(y_j-y_k)/(x_j-x_k)<2*A_i$

$上式为j,k(j>k) 两点的斜率,维护单调队列,队列中斜率单增即可$

A单增,每次弹出队首斜率小于A的部分,剩下的第一个即为答案

若加入新点i,使kj,ji斜率下降,对于每个A,若kj斜率大于A,则j不如k优,否则ji斜率一定小于A,j不如i优,那么一定可以弹出j,故维护斜率单增的队列是合法的。

 

技术分享图片
 1 //Achen
 2 #include<algorithm>
 3 #include<iostream>
 4 #include<cstring>
 5 #include<cstdlib>
 6 #include<vector>
 7 #include<cstdio>
 8 #include<queue>
 9 #include<cmath>
10 #include<set>
11 #include<map>
12 #define For(i,a,b) for(int i=(a);i<=(b);i++)
13 #define Rep(i,a,b) for(int i=(a);i>=(b);i--)
14 const int N=50007;
15 typedef long long LL; 
16 typedef double db;
17 using namespace std;
18 int n,ql,qr;
19 LL L,sum[N],c[N],f[N];
20 
21 template<typename T> void read(T &x) {
22     char ch=getchar(); x=0; T f=1;
23     while(ch!=-&&(ch<0||ch>9)) ch=getchar();
24     if(ch==-) f=-1,ch=getchar();
25     for(;ch>=0&&ch<=9;ch=getchar()) x=x*10+ch-0; x*=f;
26 }
27 
28 struct node {
29     int x,pos;
30     node(){}
31     node(int x,int pos):x(x),pos(pos){}
32 }que[N];
33 
34 LL pf(LL x) { return x*x; }
35 int ck(int i,int j,int pos) { 
36     return f[i]+pf((LL)pos-i-1-L+sum[pos]-sum[i])<=f[j]+pf((LL)pos-j-1-L+sum[pos]-sum[j]);
37 }
38 
39 //#define DEBUG
40 int main() {
41 #ifdef DEBUG
42     freopen("1.in","r",stdin);
43     //freopen(".out","w",stdout);
44 #endif
45     read(n); read(L);
46     For(i,1,n) { read(c[i]); sum[i]=sum[i-1]+c[i]; }
47     ql=1; qr=1;
48     que[ql]=node(0,1);
49     For(i,1,n) {
50         while(ql<qr&&que[ql+1].pos-1<i) ql++;
51         int j=que[ql].x;
52         f[i]=(f[j]+pf((LL)i-j-1-L+sum[i]-sum[j]));
53         while(ql<=qr&&ck(i,que[qr].x,que[qr].pos)) qr--;
54         if(ql>qr) que[++qr]=node(i,1);
55         else {
56             int l=que[qr].pos,r=n,pos=-1,j=que[qr].x;
57             while(l<=r) {
58                 int mid=((l+r)>>1);
59                 if(ck(i,j,mid)) pos=mid,r=mid-1;
60                 else l=mid+1;
61             }
62             if(pos!=-1) que[++qr]=node(i,pos);
63         }
64     }
65     printf("%lld\n",f[n]);
66     return 0;
67 }
决策单调性
技术分享图片
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define eps 1e-10
const int N=50007;
typedef long long LL; 
typedef double db;
using namespace std;
int n,ql,qr,que[N];
LL L,sum[N],c[N],f[N];
db x[N],y[N];

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!=-&&(ch<0||ch>9)) ch=getchar();
    if(ch==-) f=-1,ch=getchar();
    for(;ch>=0&&ch<=9;ch=getchar()) x=x*10+ch-0; x*=f;
}

db pf(db x) { return x*x; } 
db get_xl(int a,int b) {
    return (y[a]-y[b])/(x[a]-x[b]);
}
LL calc(int j,int i) { return f[j]+pf((LL)i-j-1-L+sum[i]-sum[j]); } 

//#define DEBUG
int main() {
#ifdef DEBUG
    freopen("1.in","r",stdin);
    //freopen(".out","w",stdout);
#endif
    read(n); read(L);
    For(i,1,n) { read(c[i]); sum[i]=sum[i-1]+c[i]; }
    ql=1; que[qr=1]=0;
    For(i,1,n) {
        db A=i+sum[i]-1-L;
        while(ql<qr&&get_xl(que[ql+1],que[ql])<A*2.0) ql++;
        f[i]=calc(que[ql],i);
        x[i]=sum[i]+i;
        y[i]=pf(sum[i]+i)+f[i];
        while(ql<qr&&get_xl(i,que[qr])+eps<get_xl(que[qr],que[qr-1])) qr--;
        que[++qr]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}
斜率优化

 

3.凸包优化

对于f[i]=x[j]*a[i]+y[j]*b[i]这样的转移方程

若决策直线斜率满足单调性则可以做到O(n)

否则需要用数据结构维护凸包,

平衡树维护凸包的模板题货币兑换

 

第j天能购买的A,B券

$A:x=(Rt_k*f_j)/(Rt_k*A_k+B_k)$

$B:y=f_j/(Rt_k*A_k+B_k)$

$f_i=MAX(x*B_i+y*A_i)$

设$t_i=max_{j<=i}(f_j)$

$w_k=Rt_k*A_k+B_k$

$f_i=MAX(t_k*(B_i/W_k+Rt_k*A_i/W_k))$

$x_i=t_i/W_i,y_i=x_i*Rt_i$

$f_i=MAX(x_k*B_i+y_k*A_i)$

$设p=x_k*B_i+y_k*A_i$

$y_k=-B_i/A_i*x_k+p/A_i$

A>0,p最大即纵截距最大,用splay维护上凸壳即可

一开始不知道怎么着脑抽硬要维护右上凸壳,有猫病吧我

码力是真的弱,主要就是很多地方没有想清楚就开敲,到底什么时候需要弹什么点
然后代码要写得具有可读性,写太丑是真的自己都看不下去,bug根本de不出来

错误示范:80分的丑陋的代码

 int y=pre(i),z=nxt(i);
        //if(!z&&y&&dcmp((q[i].y-q[y].y)/(q[i].x-q[y].x))>=0) { del(i); continue; }
        //else 
        if(!y&&z&&dcmp((q[z].y-q[i].y)/(q[z].x-q[i].x))>=0) { del(i); continue; }
        else if(y&&z&&dcmp(cross(q[z]-q[y],q[i]-q[y]))<=0) { del(i); continue; }
        for(;;) {
            y=pre(i); if(!y) break;
            db k=(q[i].y-q[y].y)/(q[i].x-q[y].x);
            if(!q[y].slop||(dcmp(k)<0&&dcmp(q[y].slop-k)>0)) break;
            del(y);
        }
        j=pre(i);
        if(!j) q[i].slop=0,q[i].lz=1;
        else q[i].slop=(q[i].y-q[j].y)/(q[i].x-q[j].x);
        for(;;) {
            y=nxt(i); if(!y) break;
            db k=(q[y].y-q[i].y)/(q[y].x-q[i].x);
            if((dcmp(k)<0)&&dcmp(q[i].slop-k)>0) break;
            del(y);
        }
        y=nxt(i); if(y) q[y].lz=0,q[y].slop=(q[y].y-q[i].y)/(q[y].x-q[i].x);

 正确示范:优美的代码更不容易出错

void check(int x) {
    int y=pre(x),z=nxt(x);
    int k=dcmp(cross(q[z]-q[y],q[x]-q[y]));
    if(y&&z&&k<=0) { del(x); return; } 
    for(;;) {
        y=pre(x);
        if(!y) { q[x].lslop=inf; break; } 
        db k=get_slop(q[x],q[y]);
        if(dcmp(k-q[y].lslop)<0) {
            q[y].rslop=q[x].lslop=k; break;
        } del(y);
    }
    for(;;) {
        z=nxt(x);
        if(!z) { q[x].rslop=-inf; break; }
        db k=get_slop(q[z],q[x]);
        if(dcmp(k-q[z].rslop)>0) {
            q[z].lslop=q[x].rslop=k; break;
        } del(z);
    }
}

 完整代码:

技术分享图片
  1 //Achen
  2 #include<algorithm>
  3 #include<iostream>
  4 #include<cstring>
  5 #include<cstdlib>
  6 #include<vector>
  7 #include<cstdio>
  8 #include<queue>
  9 #include<cmath>
 10 #include<set>
 11 #include<map>
 12 #define For(i,a,b) for(int i=(a);i<=(b);i++)
 13 #define Rep(i,a,b) for(int i=(a);i>=(b);i--)
 14 #define inf 1e9
 15 const int N=100007;
 16 typedef long long LL; 
 17 typedef double db;
 18 using namespace std;
 19 int n;
 20 db f[N],s,A[N],B[N],Rt[N],t[N],mx;
 21 
 22 template<typename T> void read(T &x) {
 23     char ch=getchar(); x=0; T f=1;
 24     while(ch!=-&&(ch<0||ch>9)) ch=getchar();
 25     if(ch==-) f=-1,ch=getchar();
 26     for(;ch>=0&&ch<=9;ch=getchar()) x=x*10+ch-0; x*=f;
 27 }
 28 
 29 struct pt {
 30     db x,y,lslop,rslop;
 31     pt(){}
 32     pt(db x,db y):x(x),y(y){}
 33 }q[N];
 34 #define eps 1e-10
 35 pt operator -(const pt&A,const pt&B) { return pt(A.x-B.x,A.y-B.y); } 
 36 int dcmp(db x) { if(fabs(x)<=eps) return 0; return x>0?1:-1; }
 37 db cross(pt a,pt b) { return a.x*b.y-a.y*b.x; }
 38 
 39 int p[N],ch[N][2],rt;
 40 #define lc ch[x][0]
 41 #define rc ch[x][1]
 42 void rotate(int x) {
 43     int y=p[x],z=p[y],l=(x==ch[y][1]),r=(l^1);
 44     if(z) ch[z][y==ch[z][1]]=x; p[x]=z;
 45     ch[y][l]=ch[x][r]; p[ch[x][r]]=y;
 46     ch[x][r]=y; p[y]=x;
 47 }
 48 
 49 void splay(int x,int FA) {
 50     for(;p[x]!=FA;rotate(x)) {
 51         int y=p[x],z=p[y];
 52         if(z!=FA) ((x==ch[y][1])^(y==ch[z][1]))?rotate(x):rotate(y);
 53     } if(!FA) rt=x;
 54 }
 55 
 56 void insert(int id) {
 57     int x=rt,f=0,l=0;
 58     for(;;) {
 59         if(!x) {
 60             p[id]=f; if(f) ch[f][l]=id; else rt=x;
 61             splay(id,0); break;
 62         }
 63         if(dcmp(q[x].x-q[id].x)>0) f=x,x=lc,l=0;
 64         else f=x,x=rc,l=1;
 65     }
 66 }
 67 
 68 int pre(int x) {
 69     splay(x,0); x=lc;
 70     while(rc) x=rc;
 71     return x;
 72 }
 73 
 74 int nxt(int x) {
 75     splay(x,0); x=rc;
 76     while(lc) x=lc;
 77     return x;
 78 }
 79 
 80 void del(int x) {
 81     if(!lc) { 
 82         if(x==rt) rt=rc,p[rt]=0;
 83         else ch[p[x]][x==ch[p[x]][1]]=rc,p[rc]=p[x];
 84     }
 85     else if(!rc) {
 86         if(x==rt) rt=lc,p[rt]=0;
 87         else ch[p[x]][x==ch[p[x]][1]]=lc,p[lc]=p[x];
 88     }
 89     else {
 90         int y=pre(x);
 91         int z=nxt(x);
 92         splay(y,0);
 93         splay(z,y);
 94         p[ch[z][0]]=ch[z][0]=0;
 95     }
 96 }
 97 
 98 int find(db k) {
 99     for(int x=rt;x;) {
100         int t1=dcmp(q[x].lslop-k),t2=dcmp(q[x].rslop-k);
101         if(t1>=0&&t2<=0) return x;
102         if(t1<0) x=lc;
103         else x=rc;
104     } return -1;
105 }
106 
107 db get_slop(pt A,pt B) { return (A.y-B.y)/(A.x-B.x); }
108 
109 void check(int x) {
110     int y=pre(x),z=nxt(x);
111     int k=dcmp(cross(q[z]-q[y],q[x]-q[y]));
112     if(y&&z&&k<=0) { del(x); return; } 
113     for(;;) {
114         y=pre(x);
115         if(!y) { q[x].lslop=inf; break; } 
116         db k=get_slop(q[x],q[y]);
117         if(dcmp(k-q[y].lslop)<0) {
118             q[y].rslop=q[x].lslop=k; break;
119         } del(y);
120     }
121     for(;;) {
122         z=nxt(x);
123         if(!z) { q[x].rslop=-inf; break; }
124         db k=get_slop(q[z],q[x]);
125         if(dcmp(k-q[z].rslop)>0) {
126             q[z].lslop=q[x].rslop=k; break;
127         } del(z);
128     }
129 }
130 
131 //#define DEBUG
132 int main() {
133 #ifdef DEBUG
134     freopen("1.in","r",stdin);
135     freopen("my.out","w",stdout);
136 #endif
137     scanf("%d%lf",&n,&s);
138     For(i,1,n) {
139         scanf("%lf%lf%lf",&A[i],&B[i],&Rt[i]);
140         int j=find(-B[i]/A[i]);
141         if(i!=1) f[i]=q[j].x*B[i]+q[j].y*A[i];
142         else f[i]=s; f[i]=max(f[i],f[i-1]);
143         mx=max(mx,f[i]);
144         q[i].x=mx/(Rt[i]*A[i]+B[i]); q[i].y=q[i].x*Rt[i];
145         insert(i); check(i);
146     }
147     db ans=0;
148     printf("%.3lf\n",f[n]);
149     return 0;
150 }
View Code

 

4.多重背包的nm做法

noip之前在长沙学的,印象不是很深,所以拿出来

$f_i=MAX(\sum_{k=0}^{min(i/w,c)}f[i-k*w]+k*val)$

那么每次按模w分类,每一类可以用单调队列维护

技术分享图片
 1 //Achen
 2 #include<algorithm>
 3 #include<iostream>
 4 #include<cstring>
 5 #include<cstdlib>
 6 #include<vector>
 7 #include<cstdio>
 8 #include<queue>
 9 #include<cmath>
10 #include<set>
11 #include<map>
12 #define For(i,a,b) for(int i=(a);i<=(b);i++)
13 #define Rep(i,a,b) for(int i=(a);i>=(b);i--)
14 const int N=50007;
15 typedef long long LL; 
16 typedef double db;
17 using namespace std;
18 int n,W,w[N],v[N],c[N],que[N],ql,qr;
19 LL f[2][N],ans;
20 
21 template<typename T> void read(T &x) {
22     char ch=getchar(); x=0; T f=1;
23     while(ch!=-&&(ch<0||ch>9)) ch=getchar();
24     if(ch==-) f=-1,ch=getchar();
25     for(;ch>=0&&ch<=9;ch=getchar()) x=x*10+ch-0; x*=f;
26 }
27 
28 //#define DEBUG
29 int main() {
30 #ifdef DEBUG
31     freopen("1.in","r",stdin);
32     //freopen(".out","w",stdout);
33 #endif
34     read(n); read(W);
35     For(i,1,n) {
36         read(w[i]); read(v[i]); read(c[i]);
37     }
38     int o=0;
39     For(i,1,n) {
40         o^=1;
41         memset(f[o],0,sizeof(f[o]));
42         For(d,0,w[i]-1) {
43             ql=1; qr=0;
44             for(int j=0;j*w[i]+d<=W;j++) {
45                 while(ql<=qr&&j-que[ql]>c[i]) ql++;
46                 f[o][j*w[i]+d]=f[o^1][j*w[i]+d];
47                 if(ql<=qr) 
48                     f[o][j*w[i]+d]=max(f[o][j*w[i]+d],f[o^1][que[ql]*w[i]+d]+(j-que[ql])*v[i]);
49                 while(ql<=qr&&f[o^1][que[qr]*w[i]+d]-que[qr]*v[i]<=f[o^1][j*w[i]+d]-j*v[i]) qr--;
50                 que[++qr]=j;    
51             }
52         }
53     }
54     printf("%lld\n",f[o][W]);
55     return 0;
56 }
View Code

 

 

 

 
























































以上是关于几个dp的陈年老题的主要内容,如果未能解决你的问题,请参考以下文章

分享一些学习安卓时的陈年老代码,以及其它一些java练习代码

Vue.js升级小记

几个相似的DP题

HDU2457 DNA repair(AC自动机+DP)

0x02 枚举模拟递推

HDU4057 Rescue the Rabbit(AC自动机+状压DP)