斜率优化初步
首先强调一点斜率优化不一定只用于 dp,它还可以用于很多可以看作斜率的式子中,所以说我们遇到了就具体分析。
What is 斜率优化?
什么是斜率优化?我们来看这样一个问题,现在有一个平面直角坐标系,在其第一象限有很多的点,现在给定一条确定斜率
很显然,我们可以打暴力,把每个点都去计算一个
我们先思考什么样的点一定不可以:
对于图中的
Proof: 我们令
A,B,C 的坐标分别为(x_1,y_1),(x_2,y_2),(x_3,y_3) ,其中x_1<x_2<x_3 ,那么它们之间的几何关系就是k_{AB}=\frac{y_2-y_1}{x_2-x_1}>k_{BC}=\frac{y_3-y_2}{x_3-x_2} 。为了证明
B 是完全无用的,换句话说我们就是要证明对于任意k 都满足:
很显然,这个式子正向证明是困难的,我们考虑反证。
我们先假设
B 比A 优,那么它的条件就是b_B<b_A ,把这个式子展开就是y_2-kx_2<y_1-kx_1 ,稍微整理一下就是\frac{y_2-y_1}{x_2-x_1}<k ,即k_{AB}<k 。同理,我们假设
B 比A 优,那么它的条件就是\frac{y_3-y_2}{x_3-x_2}>k ,即k_{BC}>k 。如果说
B 是有用的,那么就应该让B 同时满足上述的两个条件,即B 要比A,B 都更优, 那么其条件就是k_{AB}<k<k_{BC} ,但是我们最开始已经规定k_{AB}>k_{BC} ,矛盾,故B 一定无用。
因此我们可以基于此来维护可能为答案的点,我们来观察一下性质:
(图中红线为真正维护的)
不难发现,这样的相邻的两个点之间的斜率是递增的,所以说我们可以用一个单调队列来进行维护,但是如果存储当前点和前一个点之间的斜率的话,是可能会有精度问题的,所以说我们考虑维护每个点的坐标,然后计算斜率时直接用坐标进行交叉相乘然后比较大小即可。
然后考虑对于新加入的一个点,也是直接和单调队列的末端两个点进行上述的比较,如果说末尾的元素满足上述
:::info[Code]{open}
struct Node{ll x,y;}q[INF];
int head=1,tail;
namespace Slope{
inline void update(Node c){
while (tail-head>=1){
Node a=q[tail-1],b=q[tail];
if ((c.y-b.y)*(b.x-a.x)<=(b.y-a.y)*(c.x-b.x))tail--;
else break;
}
q[++tail]=c;
}
}
:::
现在的问题就转换为如何快速求的最优点,这个其实也是简单的,我们思考一下最优点的性质。我们假设最优点为
再加上当前使用的是单调队列,所以说其具有单调性,所以说我们可以采用类似于二分答案的方式进行查找答案。比如说当前枚举到的点是
这样时间复杂度就是
:::info[Code]{open}
struct Node{ll x,y;}q[INF];
int head=1,tail;
namespace Slope{
inline bool check(int a,int b,ll k){return (q[b].y-q[a].y)<=k*(q[b].x-q[a].x);}
inline int find_best(ll k){
if (tail==head)return head;
int l=head+1,r=tail,res=head;
while (l<=r){
int mid=(l+r)>>1;
if (check(mid-1,mid,k))res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
inline void update(Node c){
while (tail-head>=1){
Node a=q[tail-1],b=q[tail];
if ((c.y-b.y)*(b.x-a.x)<=(b.y-a.y)*(c.x-b.x))tail--;
else break;
}
q[++tail]=c;
}
}
:::
当然这里也有一个小小的优化,如果说多条直线的斜率
假设我们有两个决策点
j_1 和j_2 (满足x_{j_1} < x_{j_2} ),且当前查询斜率为k_i 。在我们刚才维护的过程中,如果
j_2 比j_1 更优,根据之前的推导,必须满足:K(j_1, j_2) = \frac{y_{j_2} - y_{j_1}}{x_{j_2} - x_{j_1}} \le k_i 因为
k 是单调递增的,所以满足k_{i+1} \ge k_i 。既然
K(j_1, j_2) \le k_i 已经成立,那么显然有:K(j_1, j_2) \le k_{i+1} 这说明如果
j_2 在当前时刻已经胜过了j_1 ,那么在未来斜率更大的时刻,j_2 依然会胜过j_1 。
那么
:::info[Code]{open}
struct Node{ll x,y;}q[INF];
int head=1,tail;
namespace Slope{
inline int get_best(Node c){
while (tail>head){
Node a=q[head],b=q[head+1];
if ((c.y-a.y)*(c.x-b.x)<(c.x-a.x)*(c.y-b.y))head++;
else break;
}
return head;
}
inline void update(Node c){
while (tail-head>=1){
Node a=q[tail-1],b=q[tail];
if ((c.y-b.y)*(b.x-a.x)<=(b.y-a.y)*(c.x-b.x))tail--;
else break;
}
q[++tail]=c;
}
}
:::
:::warning{open}
无论是二分还是单调队列,其都满足
| 横坐标 x | 查询斜率 k | 维护工具 | 复杂度 |
|---|---|---|---|
| 单调 | 单调 | 单调队列 (head/tail 双向操作) | |
| 单调 | 不单调 | 单调队列 + 二分查找 | |
| 不单调 | 单调/不单调 | 李超线段树 / CDQ 分治 |
一个实战技巧:在时间复杂度允许的情况下,其实都可以写李超线段树或者二分,因为其最为完备。 :::
我们回顾上述的点所构成的样子,不难发现这是一个下凸壳,那么有没有可能维护上凸壳呢?这是很显然是有的,维护上凸壳就是使
这里有初学者经常犯的一个问题,
最后我们思考一个简单的问题,什么时候维护上凸壳,什么时候维护下凸壳呢?这个答案也是很显然的,通常,我们要求得式子是在
当然这个不是绝对的,有可能我们要求得这个式子就是一个类似于斜率的东西,那么我们就可以选择画出点集和边集,来判断具体是应该维护上凸壳还是下凸壳(下面有例题)。
那么我们回到最初的问题,什么是斜率优化?这个就是斜率优化,通过单调队列在较快的时间内完成对于最小(或最大)
斜率优化的应用
最常见的应用就是用于优化动态规划,我们以一道例题入手:
例题 1(下凸壳)
P3195 [HNOI2008] 玩具装箱 - 洛谷
我们先思考出暴力的状态转移方程式长成啥样?不难发现,我们设
其中
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=5e4+10;
ll n,L,sum[INF],dp[INF];
namespace yixing{
inline void Main(){
cin>>n>>L;
for (int i=1;i<=n;i++){
int c;cin>>c;
sum[i]=sum[i-1]+c+1;
}
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for (int i=1;i<=n;i++){
for (int j=0;j<i;j++)dp[i]=min(dp[i],dp[j]+(sum[i]-sum[j]-1-L)*(sum[i]-sum[j]-1-L));
}
cout<<dp[n];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
然后我们就考虑优化,我们现在的式子长成这样:
考虑将其展开,同时省去
我们的斜率优化部分是在对着一条直线做操作,所以说我们这里也应该把它化成一个直线的样子,即化成
不难发现,当
同时我们又注意到,这些常数项是一定无法放在
我们现在考虑如何再凑出一个
由于在斜率优化部分,每一条直线的
现在这个式子就长成这样:
现在所有的项都处理出来了,那么就可以直接套斜率优化的模板了。这里就等价于每一次给定的直线为上面这一条,然后就可以计算出来
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=5e4+10;
ll n,L,sum[INF],dp[INF];
struct Node{ll a,b;}q[INF];
int head,tail;
namespace Slope{
inline bool check(int a,int b,ll k){return (q[b].b-q[a].b)<=k*(q[b].a-q[a].a);}
inline int get_best(ll k){
if (head==tail)return head;
int l=head+1,r=tail,res=head;
while (l<=r){
int mid=(l+r)>>1;
if (check(mid-1,mid,k))res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
inline void update(Node c){
while (tail>head){
Node a=q[tail-1],b=q[tail];
if ((b.b-a.b)*(c.a-b.a)>=(b.a-a.a)*(c.b-b.b))tail--;
else break;
}
q[++tail]=c;
}
}
namespace yixing{
inline void Main(){
cin>>n>>L;
for (int i=1;i<=n;i++){
int c;cin>>c;
sum[i]=sum[i-1]+c+1;
}
q[++tail]={0,0};
for (int i=1;i<=n;i++){
int j=Slope::get_best(2*sum[i]-2*L-2);
ll x=q[j].a,y=q[j].b;
ll b=y-(2*sum[i]-2*L-2)*x;
dp[i]=b-(2*sum[i]+2*sum[i]*L-sum[i]*sum[i]-L*L-2*L-1);
Node ne_pos={sum[i],dp[i]+sum[i]*sum[i]};
Slope::update(ne_pos);
}
cout<<dp[n];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
这个就是一个标准的斜率优化 dp 的问题,其实上来说,列出基础的状态转移方程,其实就已经做完一大半了,剩下的就几乎是模板了。
我们再来看一道题嘛。
例题 2(上凸壳)
P3628 [APIO2010] 特别行动队 - 洛谷
还是老套路,先列出暴力的状态转移方程,考虑设
其中
这样做的时间复杂度就是
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=1e6+10;
ll n,a,b,c;
ll x[INF],sum[INF],dp[INF];
namespace yixing{
inline void Main(){
cin>>n>>a>>b>>c;
for (int i=1;i<=n;i++)cin>>x[i],sum[i]=sum[i-1]+x[i];
memset(dp,-0x3f,sizeof(dp));
dp[0]=0;
for (int i=1;i<=n;i++){
for (int j=0;j<i;j++){
ll X=sum[i]-sum[j];
dp[i]=max(dp[i],dp[j]+(a*X*X+b*X+c));
}
}
cout<<dp[n];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
然后考虑斜率优化,将式子展开有:
简单合并一下并且按照
然后套斜率优化的模板就可以了。
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=1e6+10;
ll n,a,b,c;
ll x[INF],sum[INF],dp[INF];
struct Node{ll a,b;}q[INF];
int head=1,tail;
namespace Slope{
inline bool check(int a,int b,ll k){return (q[b].b-q[a].b)>=k*(q[b].a-q[a].a);}
inline int get_best(ll k){
if (tail==head)return head;
int l=head+1,r=tail,res=head;
while (l<=r){
int mid=(l+r)>>1;
if (check(mid-1,mid,k))res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
inline void update(Node c){
while (tail>head){
Node a=q[tail-1],b=q[tail];
if ((b.b-a.b)*(c.a-b.a)<=(b.a-a.a)*(c.b-b.b))tail--;
else break;
}
q[++tail]=c;
}
}
namespace yixing{
inline void Main(){
cin>>n>>a>>b>>c;
for (int i=1;i<=n;i++)cin>>x[i],sum[i]=sum[i-1]+x[i];
q[++tail]={0,0};
for (int i=1;i<=n;i++){
ll k=2*a*sum[i];
int j=Slope::get_best(k);
ll x=q[j].a,y=q[j].b;
ll b1=y-k*x;
dp[i]=b1+(b*sum[i]+c+a*sum[i]*sum[i]);
Node ne_p={sum[i],dp[i]+a*sum[i]*sum[i]-b*sum[i]};
Slope::update(ne_p);
}
cout<<dp[n];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
例题 3(多层优化)
斜率优化只能将
P4072 [SDOI2016] 征途 - 洛谷
按照常规,先推暴力,发现这个式子有点难弄,所以说我们考虑对方差做一下操作,有
展开并合并得:
然后乘上
不难发现后者是一个常数,那么我们得目的就是要使前者最小化,那么就很简单了,我们设
其中后者又可以用前缀和来维护,所以说有:
那么,我们先枚举
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=3e3+10;
ll n,m,v[INF];
ll dp[INF][INF],sum[INF];
namespace yixing{
inline void Main(){
cin>>n>>m;
for (int i=1;i<=n;i++)cin>>v[i],sum[i]=sum[i-1]+v[i];
memset(dp,0x3f,sizeof(dp));
ll LIM=dp[0][0];
dp[0][0]=0;
for (int k=1;k<=m;k++)for (int i=1;i<=n;i++)for (int j=0;j<i;j++)if (dp[j][k-1]!=LIM)dp[i][k]=min(dp[i][k],dp[j][k-1]+(sum[i]-sum[j])*(sum[i]-sum[j]));
cout<<dp[n][m]*m-sum[n]*sum[n];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
将上面这个式子展开,并改成
然后就套斜率优化的模板即可。
但是这里要注意一个点,
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=3e3+10;
ll n,m,v[INF];
ll dp[INF][INF],sum[INF];
struct Node{ll a,b;}q[INF];
int head=1,tail;
namespace Slope{
inline bool check(int a,int b,ll k){return (q[b].b-q[a].b)<=k*(q[b].a-q[a].a);}
inline int get_best(ll k){
if (head==tail)return head;
int l=head+1,r=tail,res=head;
while (l<=r){
int mid=(l+r)>>1;
if (check(mid-1,mid,k))res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
inline void update(Node c){
while (tail>head){
Node a=q[tail-1],b=q[tail];
if ((b.b-a.b)*(c.a-b.a)>=(b.a-a.a)*(c.b-b.b))tail--;
else break;
}
q[++tail]=c;
}
}
namespace yixing{
inline void Main(){
cin>>n>>m;
for (int i=1;i<=n;i++)cin>>v[i],sum[i]=sum[i-1]+v[i];
for (int i=1;i<=n;i++)dp[i][1]=sum[i]*sum[i];
for (int k=2;k<=m;k++){
head=1,tail=0;
q[++tail]={0,0};
for (int i=1;i<=n;i++){
ll k1=2*sum[i];
int j=Slope::get_best(k1);
ll x=q[j].a,y=q[j].b;
ll b=y-k1*x;
dp[i][k]=b+sum[i]*sum[i];
Node ne_p={sum[i],sum[i]*sum[i]+dp[i][k-1]};
Slope::update(ne_p);
}
}
cout<<dp[n][m]*m-sum[n]*sum[n];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
例题 4(有限制的 j )
不难发现,前面的题目,我们枚举的
P5468 [NOI2019] 回家路线 - 洛谷
还是老套路,先做暴力。
注意到只能对着站点或者某班列车做操作,其中对着站点做操作似乎不太好处理,因为无法处理时间的问题。所以说我们对着某班列车做操作。
我们设
那么我们枚举
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MINF=1e6+10,TINF=4e4+10,NINF=1e5+10;
ll n,m,A,B,C;
ll dp[MINF];
struct Line{ll u,v,p,q;}l[MINF];
struct Node{ll a,b;};
vector<Node> q[NINF];
vector<int> t[TINF];
int now_t,head[TINF];
namespace Slope{
inline int get_best(ll k,int id){
while (q[id].size()-head[id]>=2){
Node a=q[id][head[id]],b=q[id][head[id]+1];
if ((b.b-a.b)<=k*(b.a-a.a))head[id]++;
else break;
}
return head[id];
}
inline void update(Node c,int id){
while (q[id].size()-head[id]>=2){
Node a=q[id][q[id].size()-2],b=q[id][q[id].size()-1];
if ((b.b-a.b)*(c.a-b.a)>=(c.b-b.b)*(b.a-a.a))q[id].pop_back();
else break;
}
q[id].push_back(c);
}
}
namespace yixing{
bool cmp(const Line a1,const Line a2){return a1.p<a2.p;}
inline void Main(){
cin>>n>>m>>A>>B>>C;
for (int i=1;i<=m;i++)cin>>l[i].u>>l[i].v>>l[i].p>>l[i].q;
sort(l+1,l+1+m,cmp);
l[0].u=l[0].p=l[0].q=0,l[0].v=1;
dp[0]=0;
ll ans=LONG_LONG_MAX;
t[0].push_back(0);
for (int i=1;i<=m;i++){
while (now_t<=l[i].p){
for (int j:t[now_t]){
int id=l[j].v;
Node c={l[j].q,dp[j]+A*l[j].q*l[j].q-B*l[j].q};
Slope::update(c,id);
}
now_t++;
}
int u=l[i].u;
if ((int)q[u].size()>head[u]){
ll k=2*l[i].p*A;
int j=Slope::get_best(k,u);
ll x=q[u][j].a,y=q[u][j].b;
ll b=y-k*x;
dp[i]=b+(B*l[i].p+A*l[i].p*l[i].p+C);
t[l[i].q].push_back(i);
if (l[i].v==n)ans=min(ans,dp[i]+l[i].q);
}else dp[i]=LONG_LONG_MAX;
}
cout<<ans;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
我们还是按照常规对其进行拆开,有:
然后稍加整理,有:
但是这里很显然是不能直接套用斜率优化的,因为
那么我们就考虑对着每一个站点开一个队列,用来维护到达这个站点的列车(即
最后就是要处理时间的问题了,这里也是有一个非常经典的操作,就是延迟入队。什么意思呢,简单解释来说就是计算出来
回顾我们上面所写的代码,几乎都是计算完一个
那么如果说将
那么我们就考虑如何限制一下时间:对于第
接下来我们就应该考虑如何进行延迟加点的问题了,很显然,我们可以开一个变量
注意到每个点还是只会入队出队一次,所以说时间复杂度是没有变化的。
:::info[Code]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=1e6+10;
ll n,m,A,B,C;
ll dp[INF];
struct Line{ll u,v,p,q;}l[INF];
struct Node{ll a,b;};
vector<Node> q[INF];
vector<int> t[INF];
int now_t,head[INF];
namespace Slope{
inline int get_best(ll k,int id){
while (q[id].size()-head[id]>=2){
Node a=q[id][head[id]],b=q[id][head[id]+1];
if ((b.b-a.b)<=k*(b.a-a.a))head[id]++;
else break;
}
return head[id];
}
inline void update(Node c,int id){
while (q[id].size()-head[id]>=2){
Node a=q[id][q[id].size()-2],b=q[id][q[id].size()-1];
if ((b.b-a.b)*(c.a-b.a)>=(c.b-b.b)*(b.a-a.a))q[id].pop_back();
else break;
}
q[id].push_back(c);
}
}
namespace yixing{
bool cmp(const Line a1,const Line a2){return a1.p<a2.p;}
inline void Main(){
cin>>n>>m>>A>>B>>C;
for (int i=1;i<=m;i++)cin>>l[i].u>>l[i].v>>l[i].p>>l[i].q;
sort(l+1,l+1+m,cmp);
l[0].u=l[0].p=l[0].q=0,l[0].v=1;
dp[0]=0;
ll ans=LONG_LONG_MAX;
t[0].push_back(0);
for (int i=1;i<=m;i++){
while (now_t<=l[i].p){
for (int j:t[now_t]){
int id=l[j].v;
Node c={l[j].q,dp[j]+A*l[j].q*l[j].q-B*l[j].q};
Slope::update(c,id);
}
now_t++;
}
int u=l[i].u;
if ((int)q[u].size()>head[u]){
ll k=2*l[i].p*A;
int j=Slope::get_best(k,u);
ll x=q[u][j].a,y=q[u][j].b;
ll b=y-k*x;
dp[i]=b+(B*l[i].p+A*l[i].p*l[i].p+C);
t[l[i].q].push_back(i);
if (l[i].v==n)ans=min(ans,dp[i]+l[i].q);
}else dp[i]=LONG_LONG_MAX;
}
cout<<ans;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::
例题 5(斜率式子的转化)
上述的题目全部都是一个套路,推出式子,然后化成
P1721 [NOI2016] 国王饮水记 - 洛谷
这道题毕竟还是一道黑题,肯定没有那么简单,所以说我们一步一步来分析。
Part 1:什么会对答案造成影响?
我们先思考什么样的水箱会对答案造成影响?不难发现应该是
那么我们可以在输入的时候把所有
Part 2:怎么操作才可以使的答案最大?
假设说现在有高度为
如果说先合并
如果说先合并
很显然,前者大于后者,所以说一定是先合并矮的,再去合并高的,这样才可以使得答案最大化。
所以说我们可以把
Part 3:怎么利用动态规划解决?
由于说我们现在已经完成了对
其中
这样写的时间复杂度是
Part 4:怎么优化动态规划?(关键点)
很显然这个式子我们是没有办法像之前一样进行划分的,但是我们回想一下斜率的定义式:
那么能不能也这样处理一下呢?显然是可以的。
这个式子的右侧可以分一下组,看成:
其中,有关
那么现在的问题就转化为了,给定一个确定的点
由于
又注意到
Part 5:怎么优化掉
首先有一个较为显然的事实:每一次合并,至少都要包含一个之前未连通的城市,否则水位不会上升。而除了首都之外只剩下
注意到,我们不可能每一次都去使用题目中给的高精度小数来操作(因为操作一次的时间复杂度至少都是
Part 6:怎么处理精度爆炸的问题?
设初始状态下,水位与最高城市的差距最大为
初始距离很显然为
因此,合并次数
解得:
我们考虑一下极限情况,有
然后此时你再去交,你就会发现,可以拿大概
为什么呢?还是那句话,精度要炸,尽管这样已经提升的很大一部分的精度问题了,但是请注意,即使是在 Linux 环境下,long double 的小数位数也只有
删去了高精度小数的部分,只展示核心代码。
:::info[Code]
#define ll long long
#define ld long double
const int INF=8100;
using namespace std;
Decimal ans;
int n,k,p,h[INF],tot=1;//tot 表示有多少个数大于 h1
int pos[INF][20];//pos_il 表示在 i 这个位置,第 l 次操作了的点
ll sum[INF];
ld dp[INF][20];
struct Node{ld a,b;}q[INF];
int head=1,tail=0,id[INF];
namespace Slope{
inline int get_best(Node c){
while (tail>head){
Node a=q[id[head]],b=q[id[head+1]];
if ((c.b-a.b)*(c.a-b.a)<(c.a-a.a)*(c.b-b.b))head++;
else break;
}
return head;
}
inline void udpate(Node c,int p){
while (tail>head){
Node a=q[id[tail-1]],b=q[id[tail]];
if ((b.b-a.b)*(c.a-b.a)>(b.a-a.a)*(c.b-b.b))tail--;
else break;
}
id[++tail]=p;
}
}
namespace yixing{
Decimal calc(int city,int tim){
if (!tim)return h[1];
return ((calc(pos[city][tim],tim-1)+(sum[city]-sum[pos[city][tim]]))/(city-pos[city][tim]+1));
}
inline void Main(){
cin>>n>>k>>p>>h[1];
for (int i=2;i<=n;i++){
int tmp;cin>>tmp;
if (tmp>h[1])h[++tot]=tmp;
}
n=tot;
sort(h+1,h+1+n);
for (int i=1;i<=n;i++)sum[i]=sum[i-1]+h[i];
for (int i=1;i<=n;i++)dp[i][0]=h[1];
k=min(k,n);
int lim=min(k,18);
for (int l=1;l<=lim;l++){
head=1,tail=0;
id[++tail]=1;
for (int i=1;i<=n;i++)q[i]={1.0*(i-1),(sum[i]-dp[i][l-1])};
for (int i=2;i<=n;i++){
Node u={1.0*i,1.0*sum[i]};
int j=id[Slope::get_best(u)];
pos[i][l]=j;
dp[i][l]=(sum[i]-(sum[j]-dp[j][l-1]))/(i-(j-1));
Slope::udpate(q[i],i);
}
}
int rest=n-k+lim,tim;
ld maxn=0;
for (int l=0;l<=lim;l++)if (dp[rest][l]>maxn)maxn=dp[rest][l],tim=l;
ans=calc(rest,tim);
for (int i=rest+1;i<=n;i++)ans=(ans+h[i])/2;
cout<<ans.to_string(2*p);
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
yixing::Main();
return 0;
}
:::