u p d upd upd:本文相关参考资料 link
目录
引入
首先,我们经常会看到形如下面的DP式子:(
m
i
n
min
min 同理)
f
i
=
max
0
≤
j
<
i
−
1
f
j
+
b
j
+
a
i
f_i = \max_{0 \le j \lt i-1} f_j +b_j+a_i
fi=0≤j<i−1maxfj+bj+ai
形如这样的式子,如果采用暴力的话,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2) ,很容易超时。
那么就会想到 单调队列优化DP,将
max
0
≤
j
<
i
−
1
f
j
+
b
j
\max_{0 \le j \lt i-1} f_j +b_j
max0≤j<i−1fj+bj 用单调队列,就可以做到
O
(
n
)
O(n)
O(n) 级别。
但如果DP式子形如下呢:(
m
i
n
min
min 同理)
f
i
=
max
0
≤
j
<
i
−
1
f
j
+
b
j
∗
a
i
f_i = \max_{0 \le j \lt i-1} f_j + b_j*a_i
fi=0≤j<i−1maxfj+bj∗ai
此时,式子中有一项同时包含
i
i
i,
j
j
j,用单调队列的话不足以维护此式。
那么此时一般会有以下三种解决方案:
- 斜率优化
- 李超线段树
- 决策单调性
但本文只讲解 “1.斜率优化” 这一方法。
例题1 lgP3195 [HNOI2008] 玩具装箱
题意
有
n
n
n 个玩具,每个玩具的长度为
C
i
C_i
Ci ,要求将这
n
n
n 个玩具装进若干个容器中,且每个容器中的玩具的编号是连续的。
假设某个容器中装入玩具
i
i
i ~
j
j
j (
i
<
j
i \lt j
i<j) ,那么这个容器的长度为
x
=
j
−
i
+
∑
k
=
i
j
C
k
x = j - i +\sum_{k=i}^{j} C_k
x=j−i+∑k=ijCk ,费用为
(
x
−
L
)
2
(x-L)^2
(x−L)2 。
希望所有容器的总费用最小,求这个最小值。
- n n n, C i C_i Ci , L L L 题目给出
- 1 ≤ n ≤ 5 × 1 0 4 1 \le n \le 5 \times 10^4 1≤n≤5×104, 1 ≤ L ≤ 1 0 7 1 \le L \le 10^7 1≤L≤107, 1 ≤ C i ≤ 1 0 7 1 \le C_i \le 10^7 1≤Ci≤107
思路
显然本人认为直接将原理不方便理解,于是我们便从例题入手。
看题,一眼DP。
于是有状态
f
i
f_i
fi :把前 个玩具装箱所需最小费用。同时有
s
u
m
i
sum_i
sumi :
C
i
C_i
Ci的前缀和。
很容易得出转移方程:
f
i
=
min
0
≤
j
<
i
f
j
+
(
i
−
j
+
1
+
s
u
m
i
−
s
u
m
j
−
L
)
2
f_i = \min_{0 \le j \lt i} f_j+(i-j+1+sum_i-sum_j-L)^2
fi=0≤j<iminfj+(i−j+1+sumi−sumj−L)2
缩减一下式子,我们令:
g
i
=
i
+
s
u
m
i
g_i = i+sum_i
gi=i+sumi
b
i
=
i
+
s
u
m
i
+
L
+
1
b_i = i+sum_i + L + 1
bi=i+sumi+L+1
得到缩减后的式子:
f
i
=
min
0
≤
j
<
i
f
j
+
(
g
i
−
b
j
)
2
f_i = \min_{0 \le j \lt i} f_j+(g_i-b_j)^2
fi=0≤j<iminfj+(gi−bj)2
为了方便观察,我们把
i
i
i 和
j
j
j 的项分离开来。
得到:
f
i
=
g
i
2
+
min
0
≤
j
<
i
f
j
+
b
j
2
−
2
g
i
b
j
f_i = g_i^2 + \min_{0 \le j \lt i} f_j + b_j^2 - 2g_ib_j
fi=gi2+0≤j<iminfj+bj2−2gibj
显然看出,
−
2
g
i
b
j
- 2g_ib_j
−2gibj 不可以
O
(
1
)
O(1)
O(1) 做到,即需要
O
(
n
2
)
O(n^2)
O(n2)。
下面研究
j
>
k
j\gt k
j>k 时
f
i
j
<
f
i
k
f_{i_{j}} \lt f_{i_{k}}
fij<fik(意思是:
f
i
f_i
fi 通过
j
j
j 转移得到的结果 优于
f
i
f_i
fi 通过
k
k
k 转移得到的结果) 的前提条件:
g
i
2
+
f
j
+
b
j
2
−
2
g
i
b
j
<
g
i
2
+
f
k
+
b
k
2
−
2
g
i
b
k
g_i^2 + f_{j} + b_{j}^2 - 2g_ib_{j} \lt g_i^2 + f_k + b_k^2 - 2g_ib_k
gi2+fj+bj2−2gibj<gi2+fk+bk2−2gibk
令
y
i
=
f
i
−
b
i
2
y_i = f_i -b_i^2
yi=fi−bi2 ,
x
i
=
b
i
x_i = b_i
xi=bi
因为
x
i
x_i
xi 单调递增,通过移项整理得:
y
j
−
y
k
x
j
−
x
k
<
2
g
i
\frac{y_j - y_k} {x_j - x_k} \lt 2g_i
xj−xkyj−yk<2gi
发现这条式子很像求
k
k
k,
j
j
j两点的斜率,所以我们可以用 斜率优化
解决。
注意这里 2 g i 2g_i 2gi 满足单调性。
又令 T ( i , j ) = y j − y i x j − x i T(i,j) = \frac{y_j - y_i} {x_j - x_i} T(i,j)=xj−xiyj−yi
当 j > k j\gt k j>k 时,若 j j j 决策比 k k k 决策优,则满足 T ( i , k ) < 2 g i T(i,k) \lt 2g_i T(i,k)<2gi。
下面给出两个结论:
所以代码即类似 单调队列优化DP 的写法:
这样的时间复杂度就为
O
(
n
)
O(n)
O(n) 了。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e4+5;
int n,q[maxn];
ll L,a[maxn],sum[maxn],g[maxn],b[maxn],f[maxn];
double X(int i){ return b[i]; }
double Y(int i){ return f[i]+b[i]*b[i]; }
double Slope(int i,int j){
return (Y(j)-Y(i))/(X(j)-X(i));
}
int main(){
cin>>n>>L;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=sum[i-1]+a[i];
g[i]=i+sum[i];
b[i]=sum[i]+i+L+1;
}
b[0]=L+1;//!!!
int hd=1,tal=1;
for(int i=1;i<=n;i++){
while(hd<tal&&Slope(q[hd],q[hd+1])<2*g[i]) hd++;//1.删头
int j=q[hd]; f[i]=f[j]+(g[i]-b[j])*(g[i]-b[j]);//2.取头
while(hd<tal&&Slope(q[tal-1],q[tal])>Slope(q[tal],i)) tal--;//3.删尾
q[++tal]=i;//4.插入
}
cout<<f[n];
return 0;
}
练习题1 lgP3628 [APIO2010] 特别行动队
题意
有
n
n
n 个士兵,将这些士兵编成若干支特别行动队,且每支队伍的士兵编号连续。
每个士兵有战斗值
x
i
x_i
xi ,设
X
=
X =
X= 队内士兵的战斗力之和,那么一支队伍的战斗力
X
′
=
a
X
2
+
b
X
+
c
X\rq = aX^2 + bX + c
X′=aX2+bX+c 。
求出所有队伍的战斗力之和的最大值。
- n n n, x i x_i xi, a a a, b b b, c c c 题目给出
- 1 ≤ n ≤ 1 0 6 1 \le n \le 10^6 1≤n≤106, − 5 ≤ a ≤ − 1 -5 \le a \le -1 −5≤a≤−1, − 1 0 7 ≤ b ≤ 1 0 7 -10^7 \le b \le 10^7 −107≤b≤107, − 1 0 7 ≤ c ≤ 1 0 7 -10^7 \le c \le 10^7 −107≤c≤107, 1 ≤ x i ≤ 100 1 \le x_i \le 100 1≤xi≤100
思路
大体同
例题
1
例题1
例题1 了。
首先有状态:
f
i
f_i
fi :将前
i
i
i 个士兵分组后的最大战斗力值 ,前缀和
s
u
m
i
sum_i
sumi :
1
1
1 ~
i
i
i 士兵的战斗力之和
有转移:
f
i
=
m
a
x
0
≤
j
<
i
f
j
+
s
u
m
i
2
a
+
s
u
m
j
2
a
−
2
s
u
m
i
s
u
m
j
a
+
s
u
m
i
b
−
s
u
m
j
b
+
c
f_i = max_{0\le j\lt i} f_j + sum_i^2a + sum_j^2a - 2sum_isum_ja + sum_ib-sum_jb + c
fi=max0≤j<ifj+sumi2a+sumj2a−2sumisumja+sumib−sumjb+c
然后令:
g
i
=
s
u
m
i
2
a
+
s
u
m
i
b
+
c
g_i = sum_i^2a+sum_ib+c
gi=sumi2a+sumib+c
k
i
=
f
i
+
s
u
m
i
2
a
−
s
u
m
i
b
k_i= f_i+sum_i^2a-sum_ib
ki=fi+sumi2a−sumib
整理原式得:
f
i
=
g
i
+
k
j
−
2
a
s
u
m
i
s
u
m
j
f_i = g_i + k_j - 2asum_isum_j
fi=gi+kj−2asumisumj
下面研究
j
>
j
2
j\gt j_2
j>j2 时
f
i
j
>
f
i
j
2
f_{i_{j}} \gt f_{i_{j_2}}
fij>fij2(意思是:
f
i
f_i
fi 通过
j
j
j 转移得到的结果 优于
f
i
f_i
fi 通过
j
2
j_2
j2 转移得到的结果) 的前提条件:
g
i
+
k
j
−
2
a
s
u
m
i
s
u
m
j
>
g
i
+
k
j
2
−
2
a
s
u
m
i
s
u
m
j
2
g_i + k_j - 2asum_isum_j \gt g_i + k_{j_2} - 2asum_isum_{j_2}
gi+kj−2asumisumj>gi+kj2−2asumisumj2
令
y
i
=
k
i
y_i = k_i
yi=ki ,
x
i
=
s
u
m
i
x_i = sum_i
xi=sumi
因为
x
i
x_i
xi 单调递增,通过移项整理得:
y
j
−
y
j
2
x
j
−
x
j
2
>
2
a
s
u
m
i
\frac{y_j - y_{j_2}} {x_j - x_{j_2}} \gt 2asum_i
xj−xj2yj−yj2>2asumi
因为 2 a s u m i 2asum_i 2asumi 满足单调性,所以可以斜率优化。
设
i
<
j
i\lt j
i<j ,
T
(
i
,
j
)
=
y
j
−
y
i
x
j
−
x
i
T(i,j) = \frac{y_j-y_i}{x_j-x_i}
T(i,j)=xj−xiyj−yi
所以,本题有:(类似
例题
1
例题1
例题1 推理得到)
- 最优决策取队首
- 队列中相邻决策应满足: 2 a s u m i > T ( j 1 , j 2 ) > T ( j 2 , j 3 ) > . . . > T ( j t a i l − 1 , j t a i l ) 2asum_i \gt T(j_1,j_2) \gt T(j_2,j_3) \gt ...\gt T(j_{tail-1},j_{tail}) 2asumi>T(j1,j2)>T(j2,j3)>...>T(jtail−1,jtail),其中 j 1 < j 2 < j 3 < . . . < j t a i l − 1 < j t a i l j_1 \lt j_2 \lt j_3 \lt ... \lt j_{tail-1} \lt j_{tail} j1<j2<j3<...<jtail−1<jtail
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5;
ll sum[maxn],k[maxn];
double X(int i){ return sum[i]; }
double Y(int i){ return k[i]; }
double Slope(int i,int j){ return (Y(j)-Y(i))/(X(j)-X(i)); }
int n,q[maxn];
ll a,b,c,g[maxn],f[maxn];
int main(){
cin>>n>>a>>b>>c;
for(int i=1;i<=n;i++){
ll x; cin>>x;
sum[i]=sum[i-1]+x;
g[i]=sum[i]*sum[i]*a+sum[i]*b+c;
}
int hd=1,tal=1;
for(int i=1;i<=n;i++){
while(hd<tal&&Slope(q[hd],q[hd+1])>2*a*sum[i]) hd++;
int j=q[hd]; f[i]=g[i]+k[j]-2*a*sum[i]*sum[j];
k[i]=f[i]+sum[i]*sum[i]*a-sum[i]*b;
while(hd<tal&&Slope(q[tal-1],q[tal])<Slope(q[tal],i)) tal--;
q[++tal]=i;
}
cout<<f[n];
return 0;
}
练习题2 lgP2900 [USACO08MAR] Land Acquisition G
题意
有
n
n
n 块土地,每块土地给出 长
w
i
w_i
wi 和 宽
l
i
l_i
li 。
如果单买一块土地,则价格为这块土地的面积;如果同时买若干块土地(不需要编号连续),则价格为这些土地中最大的长乘以最大的宽。
求购买所有土地所需的最小费用。
- n n n, w i w_i wi, l i l_i li 题目给出
- 1 ≤ N ≤ 5 × 1 0 4 1 \leq N \leq 5 \times 10^4 1≤N≤5×104, w i , l i ≤ 1 0 6 w_i , l_i \le 10^6 wi,li≤106
思路
思路同
例题
1
例题1
例题1 差不多,但需要点思维。
如果一块土地的宽和高都比另一块要小,那么肯定是直接合并购买,这样这一块对答案没有任何贡献。所以,可以先把这些“无用”土地给去掉。
于是,剩下的就是一个 高度递减、宽度递增 的矩形序列。
如果要合并购买,就直接挑序列中的一段区间,这样贡献答案的就只有最左边矩形的高
×
\times
× 最右边矩形的宽,中间的土地就没有贡献了。
所以可以设状态:
f
i
f_i
fi :前
i
i
i 块土地的最小花费
转移为:
f
i
=
min
0
≤
j
<
i
f
j
+
w
j
+
1
l
i
f_i = \min_{0 \le j \lt i} f_j + w_{j+1}l_i
fi=0≤j<iminfj+wj+1li
推推式子即可用斜率优化解决。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e4+5;
ll f[maxn];
struct NODE{ ll x,y; }a_[maxn],a[maxn];
double X(int i){ return a[i+1].x; }
double Y(int i){ return f[i]; }
double Slope(int i,int j){ return (Y(i)-Y(j))/(X(j)-X(i)); }
int q[maxn];
int main(){
int nn,n=0; cin>>nn;
for(int i=1;i<=nn;i++)
cin>>a_[i].x>>a_[i].y;
sort(a_+1,a_+nn+1,[](NODE a,NODE b){ return a.x!=b.x?a.x>b.x:a.y>b.y; });
for(int i=1;i<=nn;i++)
if(!n||a_[i].y>a[n].y) a[++n]=a_[i];
int hd=1,tal=1;
for(int i=1;i<=n;i++){
while(hd<tal&&Slope(q[hd],q[hd+1])<a[i].y) hd++;
int j=q[hd]; f[i]=f[j]+a[j+1].x*a[i].y;
while(hd<tal&&Slope(q[tal-1],q[tal])>Slope(q[tal],i)) tal--;
q[++tal]=i;
}
cout<<f[n];
return 0;
}
例题2 游戏 [jzoj 4249]【五校联五7day1】游戏
题意
有一条数轴,从
0
0
0 ~
n
n
n ,每个点上都有一个分数
a
i
a_i
ai 。一个人从原点(即
0
0
0)开始向正方向走,若某次他从
i
i
i 走到
j
j
j(
i
<
j
i \lt j
i<j),那么得到分数为
a
j
×
(
j
−
i
)
a_j \times (j-i)
aj×(j−i) 。
最后必须走到
n
n
n ,求最终得分的最大值。
- n n n, a i a_i ai 题目给出
- n ≤ 1 0 5 n \le 10^5 n≤105, 0 ≤ a i ≤ 50 0 \le a_i \le 50 0≤ai≤50
思路
设
d
p
dp
dp 状态为:
f
i
=
m
a
x
0
≤
j
<
i
f
j
+
a
i
⋅
(
i
−
j
)
f_i = max_{0 \le j \lt i} f_j+a_i \cdot (i-j)
fi=max0≤j<ifj+ai⋅(i−j)
展开式子:
f
i
=
a
i
⋅
i
+
m
a
x
0
≤
j
<
i
f
j
−
a
i
⋅
j
f_i = a_i \cdot i + max_{0 \le j \lt i} f_j - a_i \cdot j
fi=ai⋅i+max0≤j<ifj−ai⋅j
下面研究
j
>
j
2
j\gt j_2
j>j2 时
f
i
j
>
f
i
j
2
f_{i_{j}} \gt f_{i_{j_2}}
fij>fij2(意思是:
f
i
f_i
fi 通过
j
j
j 转移得到的结果 优于
f
i
f_i
fi 通过
j
2
j_2
j2 转移得到的结果) 的前提条件:
f
j
−
a
i
⋅
j
>
f
j
2
−
a
i
⋅
j
2
f_j - a_i \cdot j \gt f_{j_2} - a_i \cdot {j_2}
fj−ai⋅j>fj2−ai⋅j2
令
y
i
=
f
i
y_i = f_i
yi=fi ,
x
i
=
i
x_i = i
xi=i
因为
x
i
x_i
xi 单调递增,通过移项整理得:
y
j
−
y
j
2
x
j
−
x
j
2
>
a
i
\frac{y_j - y_{j_2}} {x_j - x_{j_2}} \gt a_i
xj−xj2yj−yj2>ai
乍一看,这条式子很可以斜率优化,但是注意到
a
i
a_i
ai并不单调,不能进行斜率优化!
但是单调队列中相邻两项的单调性还是有的,所以可以使用单调队列维护,只不过不能进行删头操作。
然后可以通过 二分 找到对应的答案,时间复杂度会多一个
O
(
l
o
g
n
)
O(logn)
O(logn) 。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5;
ll f[maxn];
double X(int i){ return i; }
double Y(int i){ return f[i]; }
double Slope(int i,int j){ return (Y(j)-Y(i))/(X(j)-X(i)); }
int n,q[maxn];
ll a[maxn];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
int hd=1,tal=1;
for(int i=1;i<=n;i++){
int l=0,r=tal+1,mid;
while(l+1<r){//二分找出答案
mid=(l+r)>>1;
if(Slope(q[mid-1],q[mid])>a[i]) l=mid;
else r=mid;
}
int j=q[l]; f[i]=f[j]+a[i]*(i-j);//取答案
while(hd<tal&&Slope(q[tal-1],q[tal])<Slope(q[tal],i)) tal--;//删尾
q[++tal]=i;//插入
}
cout<<f[n];
return 0;
}
例题3 lgP4655 [CEOI2017] Building Bridges
题意
有
n
n
n 个柱子,每个柱子有两个值
h
i
h_i
hi 和
w
i
w_i
wi 。可以将第
i
i
i 根和第
j
j
j 根连在一起(
i
<
j
i \lt j
i<j),代价为
(
h
i
−
h
j
)
2
+
∑
k
=
i
+
1
j
−
1
w
k
(h_i-h_j)^2 + \sum_{k=i+1}^{j-1}w_k
(hi−hj)2+∑k=i+1j−1wk 。
若可以连若干次,求要将
1
1
1 ~
n
n
n 的柱子连在一起的最小代价。
- n n n, h i h_i hi, w i w_i wi 题目给出
- 2 ≤ n ≤ 1 0 5 2\le n\le 10^5 2≤n≤105 , 0 ≤ h i , ∣ w i ∣ ≤ 1 0 6 0\le h_i,\vert w_i\vert\le 10^6 0≤hi,∣wi∣≤106
思路
首先,设 :
f
i
f_i
fi:从
1
1
1 连到第
i
i
i 根柱子所需要的最小花费。
s
u
m
i
sum_i
sumi:
∑
j
=
1
i
w
j
\sum_{j=1}^{i} w_j
∑j=1iwj
然后可以列出转移式子:
f
i
=
m
i
n
0
≤
j
<
i
f
j
+
(
h
i
−
h
j
)
2
+
s
u
m
i
−
1
−
s
u
m
j
f_i= min_{0 \le j \lt i}f_j+(h_i-h_j)^2 + sum_{i-1}- sum_j
fi=min0≤j<ifj+(hi−hj)2+sumi−1−sumj
整理式子:
f
i
=
m
i
n
0
≤
j
<
i
(
s
u
m
i
−
1
+
h
i
2
)
+
(
f
j
+
h
j
2
−
s
u
m
j
)
−
2
h
i
h
j
f_i = min_{0 \le j \lt i}(sum_{i-1} + h_i^2 ) + (f_j + h_j^2 - sum_j ) - 2h_ih_j
fi=min0≤j<i(sumi−1+hi2)+(fj+hj2−sumj)−2hihj
设
g
i
=
s
u
m
i
−
1
+
h
i
2
g_i = sum_{i-1} + h_i^2
gi=sumi−1+hi2 ,
b
i
=
f
i
+
h
i
2
−
s
u
m
i
b_i = f_i + h_i^2-sum_i
bi=fi+hi2−sumi,化简原式得:
f
i
=
m
i
n
0
≤
j
<
i
g
i
+
b
j
−
2
h
i
h
j
f_i = min_{0 \le j \lt i}g_i + b_j - 2h_ih_j
fi=min0≤j<igi+bj−2hihj
下面研究
j
>
j
2
j\gt j_2
j>j2 时
f
i
j
<
f
i
j
2
f_{i_{j}} \lt f_{i_{j_2}}
fij<fij2(意思是:
f
i
f_i
fi 通过
j
j
j 转移得到的结果 优于
f
i
f_i
fi 通过
j
2
j_2
j2 转移得到的结果) 的前提条件:
g
i
+
b
j
−
2
h
i
h
j
<
g
i
+
b
j
2
−
2
h
i
h
j
2
g_i + b_j - 2h_ih_j \lt g_i + b_{j_2} - 2h_ih_{j_2}
gi+bj−2hihj<gi+bj2−2hihj2
化简得:
b
j
−
b
j
2
<
2
h
i
(
h
j
−
h
j
2
)
b_j - b_{j_2} \lt 2h_i (h_j - h_{j_2})
bj−bj2<2hi(hj−hj2)
令
y
i
=
b
i
y_i = b_i
yi=bi ,
x
i
=
h
i
x_i = h_i
xi=hi ,则原式变为:
y
j
−
y
j
2
<
2
h
i
(
x
j
−
x
j
2
)
y_j-y_{j_2} \lt 2h_i (x_j-x_{j2})
yj−yj2<2hi(xj−xj2)
这时候出现了问题:
- 按照正常情况,现在应该将 ( x j − x j 2 ) (x_j-x_{j2}) (xj−xj2) 除过去,但是我们不确定不等号是否需要改变,即我们不知道 x j x_j xj 与 x j 2 x_{j_2} xj2 的大小关系。我们想对 x x x 数组排序,这样就能确定大小关系了,但是这样下标 i i i 就会变化。于是,我们可以采用 cdq分治 。即,对于一个序列,可以先处理完序列的左半部分,然后计算序列左半部分对右半部分的贡献,这时序列的左半部分就可以排序保证 x i x_i xi 递增或递减了。
- 发现
h
i
h_i
hi 也没有单调性,我们当然可以使用 二分 (如 例题
2
2
2 ),但是这没有任何必要,仔细分析一下,在使用
cdq分治
中算贡献的时候,是序列的左半部分对右半部分的每个数分别计算贡献,与右半部分数的次序无关。于是可以将序列的右半部分按 h i h_i hi 排序后再计算,这样就可以保证 h i h_i hi 的单调性了。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
ll h[maxn],sum[maxn],f[maxn];
double X(int i){ return h[i]; }
double Y(int i){ return f[i]+h[i]*h[i]-sum[i]; }
double Slope(int i,int j){
if(X(j)-X(i)==0) return 1e18*1.0;//!!!!!!!!!!!!!!!不特判的话65pts
return (Y(j)-Y(i))/(X(j)-X(i));
}
bool cmp(int a,int b){ return X(a)!=X(b)?X(a)<X(b):Y(a)<Y(b); }
int q[maxn],id[maxn];
void Cdq(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
Cdq(l,mid);
for(int i=l;i<=r;i++)
id[i]=i;
sort(id+l,id+mid+1,cmp);
sort(id+mid+1,id+r+1,cmp);
int hd=1,tal=0;
for(int i=l;i<=mid;i++){
while(hd<tal&&Slope(q[tal-1],q[tal])>Slope(q[tal],id[i])) tal--;
q[++tal]=id[i];
}
for(int i=mid+1;i<=r;i++)
if(hd<=tal){
int i2=id[i];
while(hd<tal&&Slope(q[hd],q[hd+1])<2*h[i2]) hd++;
int j=q[hd]; f[i2]=min(f[i2],f[j]+(h[i2]-h[j])*(h[i2]-h[j])+sum[i2-1]-sum[j]);
}
Cdq(mid+1,r);
}
int main(){
int n; cin>>n;
for(int i=1;i<=n;i++)
cin>>h[i];
for(int i=1;i<=n;i++){
ll w; cin>>w;
sum[i]=sum[i-1]+w;
}
for(int i=2;i<=n;i++)
f[i]=1e18;
Cdq(1,n);
cout<<f[n];
return 0;
}
例题4 lgP4027 [NOI2007] 货币兑换
题意
在第一天你有
S
S
S 元钱,每一天中
A
A
A 券和
B
B
B 券的价值分别为
A
i
A_i
Ai 和
B
i
B_i
Bi(元/单位金券)。
每一天你都可以选择以下两种操作执行任意次:
- 卖出金券:你提供一个 [ 0 , 100 ] [0,100] [0,100] 内的实数 O P OP OP ,将 O P OP OP% 的 A A A 券和 O P OP OP% 的 B B B 券以当天的价值兑换为纸币;
- 买入金券:你支付 I P IP IP 元钱,将会得到价值为 I P IP IP 的金券,且满足 A A A 券和 B B B 券的比例在第 i i i 天恰好为 R a t e i Rate_i Ratei ;
问: N N N 天后最多能够获得多少元钱。
- N N N, S S S, A i A_i Ai, B i B_i Bi, R a t e i Rate_i Ratei 题目给出
- N ≤ 1 0 5 N \le 10^5 N≤105, 0 < A K ≤ 10 0 < A_K \leq 10 0<AK≤10, 0 < B K ≤ 10 0 < B_K\le 10 0<BK≤10, 0 < R a t e K ≤ 100 0 < \mathrm{Rate}_K \le 100 0<RateK≤100, M a x P r o f i t ≤ 1 0 9 \mathrm{MaxProfit} \leq 10^9 MaxProfit≤109
Tips (这个是本题的关键入手点,但是我就不告诉你!!!)
必然存在一种最优的买卖方案满足:每次买进操作使用完所有的人民币,每次卖出操作卖出所有的金券。
放一个关于 tips 的证明:
思路
设
d
p
dp
dp 状态:
f
i
f_i
fi:前
i
i
i 天的最大获利。
考虑如何转移:
若 状态
i
i
i 由 状态
j
j
j 转移得到,设
R
a
t
e
j
=
a
b
Rate_j= \frac{a}{b}
Ratej=ba,表示
A
A
A 卷数量
:
:
:
B
B
B 卷数量
=
a
:
b
= a:b
=a:b ,一共有 k 张卷。
则:
A
卷数量
=
k
⋅
a
a
+
b
=
k
⋅
1
a
+
b
a
=
k
⋅
1
1
+
b
a
=
k
⋅
1
1
+
1
R
a
t
e
j
=
k
⋅
1
R
a
t
e
j
+
1
R
a
t
e
j
=
k
⋅
R
a
t
e
j
R
a
t
e
j
+
1
\begin{aligned} A 卷数量 &=k \cdot \frac{a}{a+b} \\ &=k \cdot \frac{1}{\frac{a+b}{a}}\\ &=k \cdot \frac{1}{1+\frac{b}{a}}\\ &=k \cdot \frac{1}{1+\frac{1}{Rate_j}}\\ &=k \cdot \frac{1}{\frac{Rate_j+1}{Rate_j}}\\ &=k \cdot \frac{Rate_j}{Rate_j+1} \end{aligned}
A卷数量=k⋅a+ba=k⋅aa+b1=k⋅1+ab1=k⋅1+Ratej11=k⋅RatejRatej+11=k⋅Ratej+1Ratej
B
卷数量
=
k
⋅
b
a
+
b
=
k
⋅
1
R
a
t
e
j
+
1
(同理可得)
\begin{aligned} B 卷数量 &=k \cdot \frac{b}{a+b} \\ &=k \cdot \frac{1}{Rate_j+1} (同理可得) \end{aligned}
B卷数量=k⋅a+bb=k⋅Ratej+11(同理可得)
又有:
f
j
=
k
⋅
R
a
t
e
j
R
a
t
e
j
+
1
⋅
A
j
+
k
⋅
1
R
a
t
e
j
+
1
⋅
B
j
=
k
⋅
R
a
t
e
j
A
j
+
B
j
R
a
t
e
j
+
1
\begin{aligned} f_j &=k \cdot \frac{Rate_j}{Rate_j+1} \cdot A_j + k \cdot \frac{1}{Rate_j+1} \cdot B_j \\ &=k \cdot \frac{Rate_jA_j+B_j}{Rate_j+1} \end{aligned}
fj=k⋅Ratej+1Ratej⋅Aj+k⋅Ratej+11⋅Bj=k⋅Ratej+1RatejAj+Bj
由此得到:
k
=
f
j
⋅
R
a
t
e
j
+
1
R
a
t
e
j
A
j
+
B
j
k=f_j \cdot \frac{Rate_j+1}{Rate_jA_j+B_j}
k=fj⋅RatejAj+BjRatej+1
所以:
A
卷数量
=
f
j
⋅
R
a
t
e
j
+
1
R
a
t
e
j
A
j
+
B
j
⋅
R
a
t
e
j
R
a
t
e
j
+
1
=
f
j
⋅
R
a
t
e
j
R
a
t
e
j
A
j
+
B
j
A 卷数量 = f_j \cdot \frac{Rate_j+1}{Rate_jA_j+B_j} \cdot \frac{Rate_j}{Rate_j+1} = f_j \cdot \frac{Rate_j}{Rate_jA_j+B_j}
A卷数量=fj⋅RatejAj+BjRatej+1⋅Ratej+1Ratej=fj⋅RatejAj+BjRatej
B
卷数量
=
f
j
⋅
R
a
t
e
j
+
1
R
a
t
e
j
A
j
+
B
j
⋅
1
R
a
t
e
j
+
1
=
f
j
⋅
1
R
a
t
e
j
A
j
+
B
j
B 卷数量 = f_j \cdot \frac{Rate_j+1}{Rate_jA_j+B_j} \cdot \frac{1}{Rate_j+1} = f_j \cdot \frac{1}{Rate_jA_j+B_j}
B卷数量=fj⋅RatejAj+BjRatej+1⋅Ratej+11=fj⋅RatejAj+Bj1
得到转移方程:
f
i
=
{
S
i
=
1
max
{
f
i
−
1
,
f
j
⋅
R
a
t
e
j
A
i
+
B
i
R
a
t
e
j
A
j
+
B
j
}
1
≤
j
<
i
f_i = \begin{cases} S & i = 1 \\ \max\{f_{i-1},f_j \cdot \frac{Rate_jA_i+B_i}{Rate_jA_j+B_j} \} & 1 \le j \lt i \end{cases}
fi={Smax{fi−1,fj⋅RatejAj+BjRatejAi+Bi}i=11≤j<i
设:
g
i
=
f
i
R
a
t
e
i
A
i
+
B
i
g_i=\frac{f_i}{Rate_iA_i+B_i}
gi=RateiAi+Bifi
取
f
i
f_i
fi 重点部分可得到:
f
i
=
g
j
⋅
(
R
a
t
e
j
A
i
+
B
i
)
=
g
j
⋅
R
a
t
e
j
A
i
+
g
j
⋅
B
i
f_i=g_j \cdot (Rate_jA_i+B_i) = g_j \cdot Rate_jA_i+g_j \cdot B_i
fi=gj⋅(RatejAi+Bi)=gj⋅RatejAi+gj⋅Bi
下面研究
j
>
j
2
j\gt j_2
j>j2 时
f
i
j
>
f
i
j
2
f_{i_{j}} \gt f_{i_{j_2}}
fij>fij2(意思是:
f
i
f_i
fi 通过
j
j
j 转移得到的结果 优于
f
i
f_i
fi 通过
j
2
j_2
j2 转移得到的结果) 的前提条件:
g
j
R
a
t
e
j
A
i
+
g
j
B
i
>
g
j
2
R
a
t
e
j
2
A
i
+
g
j
2
B
i
g_jRate_jA_i+g_jB_i \gt g_{j_2}Rate_{j_2}A_i+g_{j_2}B_i
gjRatejAi+gjBi>gj2Ratej2Ai+gj2Bi
化简得:
g
j
R
a
t
e
j
−
g
j
2
R
a
t
e
j
2
>
B
i
A
i
⋅
(
−
g
j
−
(
−
g
j
2
)
)
g_jRate_j - g_{j_2}Rate_{j_2} \gt \frac{B_i}{A_i} \cdot (-g_j - (-g_{j_2}))
gjRatej−gj2Ratej2>AiBi⋅(−gj−(−gj2))
令
y
i
=
g
i
R
a
t
e
i
y_i = g_iRate_i
yi=giRatei ,
x
i
=
−
g
i
x_i = -g_i
xi=−gi ,则原式变为:
y
j
−
y
j
2
>
B
i
A
i
⋅
(
x
j
−
x
j
2
)
y_j-y_{j_2} \gt \frac{B_i}{A_i} \cdot (x_j - x_{j_2})
yj−yj2>AiBi⋅(xj−xj2)
P
r
o
b
l
e
m
1
\rm Problem1
Problem1 :因为我们无法确定
x
x
x 数组的单调性,所以可以通过 cdq分治
让
x
x
x 数组有单调性。
即,可将式子看成:
y
j
−
y
j
2
x
j
−
x
j
2
>
B
i
A
i
\frac{y_j-y_{j_2}}{x_j - x_{j_2}} \gt \frac{B_i}{A_i}
xj−xj2yj−yj2>AiBi
P r o b l e m 2 \rm Problem2 Problem2 :又因为发现 B i A i \frac{B_i}{A_i} AiBi 没有单调性,所以不能使用单调队列维护,但是可以通过 单调栈 + 二分 单调栈+二分 单调栈+二分 实现。
综上所述,本题可以看成是 例题2 + 例题3 的综合题。
g j gj gj说:做了这题,你就大概把这个专题各类型的题都见过了。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
double a[maxn],b[maxn],rate[maxn],f[maxn];
double X(int i){ return -f[i]/(rate[i]*a[i]+b[i]); }
double Y(int i){ return f[i]/(rate[i]*a[i]+b[i])*rate[i]; }
double Slope(int i,int j){
if(X(j)==X(i)) return 1e9*1.0;
return (Y(j)-Y(i))/(X(j)-X(i));
}
int q[maxn];
int Bry_Sch(int l,int r,double k){//这里的二分用了递归,直接while也可以,但是细节有点多,我还没调出来
if(l==r) return l;
int mid=(l+r)>>1;
if(Slope(q[mid],q[mid+1])>=k) return Bry_Sch(mid+1,r,k);
return Bry_Sch(l,mid,k);
}
bool cmp(int a,int b){ return X(a)!=X(b)?X(a)<X(b):Y(a)<Y(b); }
int id[maxn];
void Cdq(int l,int r){
if(l==r){
f[l]=max(f[l],f[l-1]);
return;
}
int mid=(l+r)>>1;
Cdq(l,mid);
for(int i=l;i<=mid;i++)
id[i]=i;
sort(id+l,id+mid+1,cmp);
int hd=1,tal=0;
for(int i=l;i<=mid;i++){
while(hd<tal&&Slope(q[tal-1],q[tal])<Slope(q[tal],id[i])) tal--;
q[++tal]=id[i];
}
for(int i=mid+1;i<=r;i++){
int j=q[Bry_Sch(hd,tal,b[i]/a[i])];
f[i]=max(f[i],f[j]*(rate[j]*a[i]+b[i])/(rate[j]*a[j]+b[j]));
}
Cdq(mid+1,r);
}
int main(){
int n; cin>>n>>f[1];
for(int i=1;i<=n;i++)
cin>>a[i]>>b[i]>>rate[i];
Cdq(1,n);
printf("%.3lf",f[n]);
return 0;
}
总结
遇到这类题目,首先得把朴素的 d p dp dp 模型建出来,然后再在这个暴力上进行斜率优化(一堆推式子 / 乱搞)。
以后可能还会继续补充……的吧