几个dp的陈年老题
真 陈年老题
都是基础的dp优化
主要是展现我基础薄弱,菜得抠脚
1.四边形不等式
四边形不等式:w[i][j]+w[i+1][j+1]<=w[i+1][j]+w[i][j+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 }
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 }
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 }