斜率优化DP 学习笔记

· · 个人记录

概述

斜率优化可以优化一类 1D / 1D 的动态规划,方程形如 f_i=\min f_j+A_i+B_j+C_iD_j\max 类似)。也就是有且仅有一项是与 i 有关的东西和与 j 有关的东西的乘积。此时有两种理解:

将之前的所有 jC_iD_j+B_j+f_j 看作一个一次函数,相当于有若干个一次函数 y=kx+b,找 x=C_i 处这些一次函数的最小值;

化成 f_j+B_j=-C_iD_j+f_i-A_i。令 y=f_j+B_j,k=C_i,x=-D_j,b=A_i-f_i。转移时相当于要在若干个点中找到一个点 (x,y) 使得经过这个点的的斜率为 k 的直线在 y 轴的截距最小。

对于第二种理解,需要维护出一个下凸包,每次用斜率为 k 的直线切这个凸包。当插入时 x 有序且查询的斜率有序,此时最优决策点有序且 x 有序,可以单调队列维护凸包,每次将答案之前的出队;若仅有 x 有序,需要单调栈维护凸包,每次二分。若都不有序,需要动态凸包或李超线段树(李超线段树为第一种理解)或 CDQ 分治。

例题

P3195

sum_i=\sum_{j=1}^i C_j+1,L\gets L+1,则 f_i=\min_{j=0}^{i-1}f_j+(s_i-s_j-L)^2。改写为 f_j+sum_j^2=2(s_i-L)s_j+f_i-(s_i-L)^2。由于 C_i 是正数,所以 x,k 单调递增,单调队列即可。

#include<bits/stdc++.h>
using namespace std;
int n,bp,fp,q[50005];
long long x,a[50005],f[50005];
double slope(int i,int j){
  return 1.0*(f[j]+a[j]*a[j]-f[i]-a[i]*a[i])/(a[j]-a[i]);
}
int main(){
  cin>>n>>x,x++;
  for(int i=1;i<=n;i++)cin>>a[i],a[i]+=a[i-1]+1;
  for(int i=1;i<=n;i++){
    while(bp<fp&&slope(q[bp],q[bp+1])<2*(a[i]-x))bp++;
    f[i]=f[q[bp]]+(a[i]-a[q[bp]]-x)*(a[i]-a[q[bp]]-x);
    while(bp<fp&&slope(q[fp-1],q[fp])>slope(q[fp],i))fp--;
    q[++fp]=i;
  }
  return cout<<f[n]<<'\n',0;
}

P2120

p 的前缀和为 sp1\sim i 的产品运到 i 的代价为 sum_i,则 f_i=\min_{j=0}^{i-1}f_j+sum_i-sum_j-sp_j(x_i-x_j),f_j-sum_j+x_jsp_j=x_isp_j+f_i-sum_i

注意一些细节:p_i 可能有 0,当 sp_i=sp_j 时比较 y,斜率为正无穷或负无穷。最后的答案是从最后一个有成品的工厂开始的最小值。

#include<bits/stdc++.h>
using namespace std;
int n,bp,fp,q[1000005];
long long x[1000005],p[1000005],c[1000005],sum[1000005],f[1000005],ans;
double slope(int i,int j){
  long long yj=f[j]-sum[j]+p[j]*x[j],yi=f[i]-sum[i]+p[i]*x[i];
  if(p[i]==p[j])return yj>yi?1e18:-1e18;
  return 1.0*(yj-yi)/(p[j]-p[i]);
}
int main(){
  cin>>n;
  for(int i=1;i<=n;i++)cin>>x[i]>>p[i]>>c[i],p[i]+=p[i-1],sum[i]=sum[i-1]+p[i-1]*(x[i]-x[i-1]);
  for(int i=1;i<=n;i++){
    while(bp<fp&&slope(q[bp],q[bp+1])<x[i])bp++;
    f[i]=f[q[bp]]+sum[i]-sum[q[bp]]-p[q[bp]]*(x[i]-x[q[bp]])+c[i];
    while(bp<fp&&slope(q[fp-1],q[fp])>slope(q[fp],i))fp--;
    q[++fp]=i;
  }
  ans=f[n];
  for(int i=n-1;i>=1&&p[i]==p[i+1];i--)ans=min(ans,f[i]);
  return cout<<ans<<'\n',0;
}

P2900

首先有用的地不会被另一块地严格包含。选出有用的地后,w 单调不升,l 单调不降。此时 f_{i}=\max_{j=0}^{i-1}f_j+w_{j+1}l_i,f_j=f_i-w_{j+1}l_i。令 x=-w_{j+1},y=f_j,k=l_i,b=f_i 即可。

#include<bits/stdc++.h>
using namespace std;
int n,cnt,bp,fp,q[50005];
long long f[50005];
struct node{
  long long x,y;
}a[50005],b[50005];
bool cmp(node a,node b){
  return a.x==b.x?a.y>b.y:a.x>b.x;
}
double slope(int i,int j){
  return 1.0*(f[j]-f[i])/(a[i+1].x-a[j+1].x);
}
int main(){
  cin>>n;
  for(int i=1;i<=n;i++)cin>>b[i].x>>b[i].y;
  sort(b+1,b+n+1,cmp);
  for(int i=1;i<=n;i++)if(b[i].y>a[cnt].y)a[++cnt]=b[i];
  n=cnt;
  for(int i=1;i<=n;i++){
    while(bp<fp&&slope(q[bp],q[bp+1])<a[i].y)bp++;
    f[i]=f[q[bp]]+a[q[bp]+1].x*a[i].y;
    while(bp<fp&&slope(q[fp-1],q[fp])>slope(q[fp],i))fp--;
    q[++fp]=i;
  }
  return cout<<f[n]<<'\n',0;
}

P5504

性质:划分出来的段的端点颜色相同,且等于这一段选择的颜色。否则若端点颜色不等于选择的颜色,将这一端不断向内缩直到相等一定不劣。

此时可以列出转移方程:f_i=\max_{0<j\leq i,s_i=s_j}f_{j-1}+a_j(cnt_i-cnt_j+1)^2。斜率优化,拆成 f_{j-1}+a_j(cnt_j-1)^2=2a_jcnt_i(cnt_j-1)+f_i-a_icnt_i^2。每个颜色分别维护。

有一个特殊的地方:这题要维护一个上凸包,查询斜率单调递增,相当于一个单调栈。因为被删除的点在之前已经更劣,因此只保留未被删除的点组成凸包是正确的。

#include<bits/stdc++.h>
using namespace std;
int n,cnt[10005];
long long a[100005],f[100005],p[100005];
vector<int>st[10005];
double slope(int i,int j){
  return 1.0*(f[j-1]+a[j]*(p[j]-1)*(p[j]-1)-f[i-1]-a[i]*(p[i]-1)*(p[i]-1))/(a[j]*p[j]-a[i]*p[i]);
}
int main(){
  cin>>n;
  for(int i=1;i<=n;i++)cin>>a[i],p[i]=++cnt[a[i]];
  for(int i=1;i<=n;i++){
    while(st[a[i]].size()>1&&slope(*----st[a[i]].end(),st[a[i]].back())<slope(st[a[i]].back(),i))st[a[i]].pop_back();
    st[a[i]].push_back(i);
    while(st[a[i]].size()>1&&slope(*----st[a[i]].end(),st[a[i]].back())<2*p[i])st[a[i]].pop_back();
    f[i]=f[st[a[i]].back()-1]+a[i]*(p[i]-p[st[a[i]].back()]+1)*(p[i]-p[st[a[i]].back()]+1);
  }
  return cout<<f[n]<<'\n',0;
}

AT_abc228_h

设最后出现的不同的数为 b_1,b_2,\cdots,b_k,显然最优是将 a 排序后分成 k 段,第 i 段变成 b_i,且 b_i 变成段中最大值显然更优。令 sum_i=\sum_{j=1}^i a_ic_i,sumc_i=\sum_{j=1}^i c_i,可以列出转移方程:f_i=\min_{j=0}^{i-1}f_j+a_i(sumc_i-sumc_j)-(sum_i-sum_j)+x。斜率优化,f_j+sum_j+x=a_isumc_j+f_i-a_isumc_i+sum_i。因为 a_i,sumc_j 都单调递增,可以单调队列维护凸包。

#include<bits/stdc++.h>
using namespace std;
int n,q[200005],bp,fp;
long long x,f[200005],sum[200005],sumc[200005];
struct node{
  long long a,c;
}a[200005];
bool cmp(node a,node b){
  return a.a<b.a;
}
double slope(int i,int j){
  return 1.0*(f[j]+sum[j]-f[i]-sum[i])/(sumc[j]-sumc[i]);
}
int main(){
  cin>>n>>x;
  for(int i=1;i<=n;i++)cin>>a[i].a>>a[i].c;
  sort(a+1,a+n+1,cmp);
  for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i].a*a[i].c,sumc[i]=sumc[i-1]+a[i].c;
  for(int i=1;i<=n;i++){
    while(bp<fp&&slope(q[bp],q[bp+1])<a[i].a)bp++;
    f[i]=f[q[bp]]+a[i].a*(sumc[i]-sumc[q[bp]])-(sum[i]-sum[q[bp]])+x;
    while(bp<fp&&slope(q[fp-1],q[fp])>slope(q[fp],i))fp--;
    q[++fp]=i;
  }
  return cout<<f[n]<<'\n',0;
}

P5785

sf,stf,t 的前缀和。不难设出 O(n^3) 的 DP,设 f_{i,j} 表示前 i 个分 j 段的答案,则 f_{i,j}=\min_{k=0}^{i-1}f_{k,j-1}(st_i+sj)(sf_i-sf_k)

考虑换一个角度:每增加一段,后面的所有任务的用时增加 s。此时我们不再关心段数,有 f_i=\min_{j=0}^{i-1}f_j+st_i(sf_i-sf_j)+s(sf_n-sf_j)

斜率优化,f_i=f_j+st_isf_i-st_isf_j+ssf_n-ssf_j,f_j-ssf_j=st_isf_j+f_i-st_isf_i-ssf_n

注意 st_i 不是单调递增的,所以要单调栈上二分。注意特判斜率不存在的情况。

#include<bits/stdc++.h>
using namespace std;
int n,st[300005],top;
long long s,a[300005],b[300005],f[300005];
double slope(int i,int j){
  long long yj=f[j]-s*b[j],yi=f[i]-s*b[i];
  if(f[i]==f[j])return yj>yi?1e18:-1e18;
  return 1.0*(yj-yi)/(b[j]-b[i]);
}
int main(){
  cin>>n>>s;
  for(int i=1;i<=n;i++)cin>>a[i]>>b[i],a[i]+=a[i-1],b[i]+=b[i-1];
  for(int i=1;i<=n;i++){
    int l=0,r=top;
    while(l<r){
      int mid=(l+r)>>1;
      if(slope(st[mid],st[mid+1])<a[i])l=mid+1;
      else r=mid;
    }
    f[i]=f[st[l]]+s*(b[n]-b[st[l]])+a[i]*(b[i]-b[st[l]]);
    while(top&&slope(st[top-1],st[top])>slope(st[top],i))top--;
    st[++top]=i;
  }
  return cout<<f[n]<<'\n',0;
}

P2497

设接受半径为 R_i。假设 ij 接受到信号,则 (x_i-x_j)^2+(R_i-r_j)^2=(R_i+r_j)^2,可得 R_i=\frac{(x_i-x_j)^2}{4r_j}。可以 DP,有 f_i=\min_{j=0}^{i-1}f_j+\frac{x_i-x_j}{2\sqrt{r_j}}+v_i

斜率优化,有 f_j-\frac{x_j}{2\sqrt{r_j}}=-\frac{x_i}{2\sqrt{r_j}}+f_i-v_i-\frac 1{2\sqrt{r_j}} 不单调,用李超树等方式维护即可。

#include<bits/stdc++.h>
using namespace std;
int n,tr[2000005],cnt;
long long m,x[500005],r[500005],v[500005],temp[500005];
double f[500005],k[500005],b[500005],ans=1e18;
bool check(int u,int v,int x){
  return k[u]*temp[x]+b[u]<k[v]*temp[x]+b[v];
}
void add(int pos,int nl,int nr,int g){
  int mid=(nl+nr)>>1;
  if(check(g,tr[pos],mid))swap(g,tr[pos]);
  if(check(g,tr[pos],nl))add(pos<<1,nl,mid,g);
  if(check(g,tr[pos],nr))add(pos<<1|1,mid+1,nr,g);
}
double query(int pos,int nl,int nr,int g){
  if(nl==nr)return k[tr[pos]]*temp[g]+b[tr[pos]];
  int mid=(nl+nr)>>1;
  double ans=k[tr[pos]]*temp[g]+b[tr[pos]];
  if(g<=mid)ans=min(ans,query(pos<<1,nl,mid,g));
  else ans=min(ans,query(pos<<1|1,mid+1,nr,g));
  return ans;
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),b[0]=1e18,cin>>n>>m;
  for(int i=1;i<=n;i++)cin>>x[i]>>r[i]>>v[i],temp[i]=x[i];
  sort(temp+1,temp+n+1),cnt=unique(temp+1,temp+n+1)-temp-1;
  for(int i=1;i<=n;i++)x[i]=lower_bound(temp+1,temp+cnt+1,x[i])-temp;
  for(int i=1;i<=n;i++)f[i]=i==1?v[i]:query(1,1,cnt,x[i])+v[i],k[i]=0.5/sqrt(r[i]),b[i]=f[i]-0.5*temp[x[i]]/sqrt(r[i]),add(1,1,cnt,i);
  for(int i=1;i<=n;i++)if(temp[x[i]]+r[i]>=m)ans=min(ans,f[i]);
  return cout<<setprecision(3)<<fixed<<ans<<'\n',0;
}

P4027

显然一次买卖必然用光当前所有的钱或券。设 f_i 为第 i 天最多持有多少现金,转移要么从第 i-1 天继承,要么在第 i 天发生卖出。枚举对应的买入在第 j 天,有 f_i=\max(f_{i-1},\max_{j=0}^{i-1}\frac{f_Jr_j}{r_ja_j+b_j}a_i+\frac{f_j}{r_ja_j+b_j}b_i)

\frac{f_j}{r_ja_j+b_j}=c。对于第二种转移,为 f_i=cr_ja_i+cb_i,化为 \frac {f_i}{b_i}=cr_j\frac{a_i}{b_i}+c,用李超树等方式维护即可。

#include<bits/stdc++.h>
using namespace std;
int n,p[100005],cnt,tr[400005];
double x[100005],y[100005],r[100005],temp[100005],f[100005],k[100005],b[100005];
bool check(int u,int v,int x){
  return k[u]*temp[x]+b[u]>k[v]*temp[x]+b[v];
}
void add(int pos,int nl,int nr,int g){
  int mid=(nl+nr)>>1;
  if(check(g,tr[pos],mid))swap(g,tr[pos]);
  if(check(g,tr[pos],nl))add(pos<<1,nl,mid,g);
  if(check(g,tr[pos],nr))add(pos<<1|1,mid+1,nr,g);
}
double query(int pos,int nl,int nr,int g){
  if(nl==nr)return k[tr[pos]]*temp[g]+b[tr[pos]];
  int mid=(nl+nr)>>1;
  double ans=k[tr[pos]]*temp[g]+b[tr[pos]];
  if(g<=mid)ans=max(ans,query(pos<<1,nl,mid,g));
  else ans=max(ans,query(pos<<1|1,mid+1,nr,g));
  return ans;
}
int main(){
  cin>>n>>f[0];
  for(int i=1;i<=n;i++)cin>>x[i]>>y[i]>>r[i],temp[i]=x[i]/y[i];
  sort(temp+1,temp+n+1),cnt=unique(temp+1,temp+n+1)-temp-1;
  for(int i=1;i<=n;i++)p[i]=lower_bound(temp+1,temp+cnt+1,x[i]/y[i])-temp;
  for(int i=1;i<=n;i++)f[i]=max(f[i-1],query(1,1,cnt,p[i])*y[i]),k[i]=f[i]*r[i]/(r[i]*x[i]+y[i]),b[i]=f[i]/(r[i]*x[i]+y[i]),add(1,1,cnt,i);
  return cout<<setprecision(3)<<fixed<<f[n]<<'\n',0;
}

P2305

此时考虑如何去掉这两个限制。对于 $s_j\geq s_i+l_i$,询问的是值域上的一段后缀,用后缀 BIT 套单调栈维护凸包;对于 $j$ 是 $i$ 的祖先,在 DFS 时维护祖先的信息,单调栈需要支持撤销。 ```cpp #include<bits/stdc++.h> using namespace std; int n,type,cnt,id[200005]; long long s[200005],p[200005],q[200005],l[200005],f[200005],temp[200005]; vector<int>e[200005],st[200005]; vector<pair<int,int> >d[200005]; double slope(int i,int j){ return 1.0*(f[j]-f[i])/(s[j]-s[i]); } void push(int x,int k){ while(st[x].size()>1&&slope(*----st[x].end(),st[x].back())>slope(st[x].back(),k))d[x].push_back(make_pair(st[x].back(),k)),st[x].pop_back(); st[x].push_back(k); } void pop(int x,int k){ st[x].pop_back(); while(!d[x].empty()&&d[x].back().second==k)st[x].push_back(d[x].back().first),d[x].pop_back(); } void add(int x,int k){ for(;x;x-=x&-x)push(x,k); } void del(int x,int k){ for(;x;x-=x&-x)pop(x,k); } long long query(int x,int k){ long long ans=0x3f3f3f3f3f3f3f3f; for(;x<=cnt;x+=x&-x){ if(st[x].empty())continue; int l=0,r=st[x].size()-1; while(l<r){ int mid=(l+r)>>1; if(slope(st[x][mid],st[x][mid+1])<p[k])l=mid+1; else r=mid; } ans=min(ans,f[st[x][l]]+(s[k]-s[st[x][l]])*p[k]+q[k]); } return ans; } void dfs(int pos){ if(pos!=1)f[pos]=query(lower_bound(temp+1,temp+cnt+1,s[pos]-l[pos])-temp,pos); add(id[pos],pos); for(int i=0;i<e[pos].size();i++)dfs(e[pos][i]); del(id[pos],pos); } int main(){ cin>>n>>type; for(int i=2,t;i<=n;i++)cin>>t>>s[i]>>p[i]>>q[i]>>l[i],e[t].push_back(i),s[i]+=s[t],temp[i]=s[i]; sort(temp+1,temp+n+1),cnt=unique(temp+1,temp+n+1)-temp-1; for(int i=1;i<=n;i++)id[i]=lower_bound(temp+1,temp+cnt+1,s[i])-temp; dfs(1); for(int i=2;i<=n;i++)cout<<f[i]<<'\n'; return 0; } ``` [BZOJ3251](https://hydro.ac/d/bzoj/p/2149) 对于第一问,由于两个旧房子之间要给新房子赋值使美观度单调不降,则 $g_i=\max_{j=0}^{i-1}g_j+1(A_i-A_j\geq i-j)$。令 $c_i=a_i-i$,转化为最长不下降子序列,CDQ 分治求解。 对于第二问,设 $f_i=\min_{j=0}^{i-1}f_j+\frac{(2a_j+i-j)(i-j-1)}2+a_i+b_i(c_j\leq c_i,g_i=g_j+1)$。考虑斜率优化,则有: $$\begin{aligned}f_i&=f_j+\frac{(2a_j+i-j)(i-j-1)}{2}+a_i+b_i\\&=f_j+a_j(i-j-1)+\frac{(i-j)(i-j-1)}{2}+a_i+b_i\\&=f_j+ia_j+\frac{(i-j)(i-j-1)}{2}-(j+1)a_j+a_i+b_i\\&=f_j+ia_j+\frac{i(i-1)+j^2-2ij+j}2-(j+1)a_j+a_i+b_i\\&=f_j+i(a_j-j)+\frac{j(j+1)}{2}-(j+1)a_j+a_i+b_i+\frac{i(i-1)}{2}\end{aligned}$$ 设 $d_i=\frac{i(i+1)}2-(i+1)a_i,e_i=a_i+b_i+\frac{i(i-1)}{2}$,则 $f_i=f_j+ic_j+d_j+e_i,f_j+d_j=-ic_j+f_i-e_i$。 这个转移还有 $j<i,c_j\leq c_i,g_i=g_j+1$ 的限制。先 CDQ 去掉 $j<i$ 的限制,此时可以对当前区间按 $c$ 排序,每次保留最大值组成的下凸包,单调栈上二分即可。 ```cpp #include<bits/stdc++.h> using namespace std; int n,p[100005],g[100005],maxn,st[100005],top; long long a[100005],b[100005],c[100005],d[100005],f[100005],ans=0x3f3f3f3f3f3f3f3f; bool cmp(int a,int b){ return c[a]==c[b]?a<b:c[a]<c[b]; } double slope(int i,int j){ long long yj=f[j]+d[j],yi=f[i]+d[i]; if(c[i]==c[j])return yj>yi?1e18:-1e18; return 1.0*(f[j]+d[j]-f[i]-d[i])/(c[j]-c[i]); } void solve(int l,int r){ if(l==r)return; int mid=(l+r)>>1; solve(l,mid),top=0; for(int i=l;i<=r;i++)p[i]=i; sort(p+l,p+r+1,cmp); for(int i=l,maxn=-1;i<=r;i++){ int t=p[i]; if(t<=mid){ if(g[t]<maxn||(t&&!g[t]))continue; if(g[t]>maxn)maxn=g[t],top=0; while(top>1&&slope(st[top-1],st[top])>slope(st[top],t))top--; st[++top]=t; } else{ if(g[t]<maxn+1)g[t]=maxn+1,f[t]=0x3f3f3f3f3f3f3f3f; else if(g[t]>maxn+1)continue; int l1=1,r1=top; while(l1<r1){ int mid=(l1+r1)/2; if(slope(st[mid],st[mid+1])<-t)l1=mid+1; else r1=mid; } f[t]=min(f[t],f[st[l1]]+t*c[st[l1]]+d[st[l1]]+a[t]+b[t]+1ll*t*(t-1)/2); } } solve(mid+1,r); } int main(){ memset(f,0x3f,sizeof(f)),f[0]=0,cin>>n; for(int i=1;i<=n;i++)cin>>a[i],c[i]=a[i]-i,d[i]=1ll*i*(i+1)/2-(i+1)*a[i]; for(int i=1;i<=n;i++)cin>>b[i]; solve(0,n); for(int i=1;i<=n;i++)maxn=max(maxn,g[i]); for(int i=1;i<=n;i++)if(g[i]==maxn)ans=min(ans,f[i]+(2*a[i]+n-i+1)*(n-i)/2); return cout<<maxn<<' '<<ans<<'\n',0; } ``` [AT_arc066_d](https://www.luogu.com.cn/problem/AT_arc066_d) 先考虑不带修,每个段 $(l,r]$ 的贡献是 $\frac{(r-l)(r-l+1)}2-sum_r+sum_l$,容易 DP $f_i=\max(f_{i-1},\max_{j=0}^{i-1}f_j+\frac{(i-j)(i-j-1)}2-sum_i+sum_j)$。对后一项斜率优化,化成 $ij+f_i-\frac{i(i-1)}2+sum_i=f_j+\frac{j(j+1)}2+sum_j$。横坐标和斜率单调递增,用单调栈维护。 对于带修,因为是临时修改,直接讨论这个位置选不选,不选可以拿前后缀的 DP 拼起来,选可以枚举 $x$ 所在的段。也就是对于每个 $i$,我们需要预处理 $h_i=\max_{l\leq i\leq r}\frac{(r-l+1)(r-l+2)}2+sum_r-sum_{l-1}+f_{l-1}+g_{r+1}$。 分治,每次统计跨越中点的区间对 $h$ 的贡献。对于左侧的点,枚举 $l$ 找最优的 $r$,这也可以写成斜率优化的形式,建出静态凸包后双指针,取后缀 $\max$ 贡献到 $h$。对右侧的点同理。 ```cpp #include<bits/stdc++.h> using namespace std; char buf1[2097152],*ip1=buf1,*ip2=buf1; inline int getc(){ return ip1==ip2&&(ip2=(ip1=buf1)+fread(buf1,1,2097152,stdin),ip1==ip2)?EOF:*ip1++; } template<typename T>void in(T&a){ T ans=0; bool f=0; char c=getc(); for(;c<'0'||c>'9';c=getc())if(c=='-')f=1; for(;c>='0'&&c<='9';c=getc())ans=ans*10+c-'0'; a=f?-ans:ans; } template<typename T,typename ...Args>void in(T&a,Args&...args){ in(a),in(args...); } int n,m,st[300005],top; long long a[300005],y,pre[300005],suf[300005],f[300005],g[300005],h[300005]; double slope1(int i,int j){ return 1.0*(f[j]+pre[j]+1ll*j*(j-1)/2-f[i]-pre[i]-1ll*i*(i-1)/2)/(j-i); } double slope2(int i,int j){ return -1.0*(g[j]+suf[j]+1ll*j*(j+1)/2-g[i]-suf[i]-1ll*i*(i+1)/2)/(j-i); } void calc(int n,long long a[]){ top=st[0]=0; for(int i=1;i<=n;i++){ pre[i]=pre[i-1]+a[i]; while(top>=1&&slope1(st[top-1],st[top])<i)top--; f[i]=max(f[i-1],f[st[top]]+1ll*(i-st[top])*(i-st[top]+1)/2-pre[i]+pre[st[top]]); while(top>=1&&slope1(st[top-1],st[top])<slope1(st[top],i))top--; st[++top]=i; } } void solve(int l,int r){ if(l==r){ h[l]=1-a[l]; return; } int mid=(l+r)>>1; long long now=-0x3f3f3f3f3f3f3f3f; solve(l,mid),solve(mid+1,r),top=0,st[top]=l-1; for(int i=l;i<=mid;i++){ while(top>=1&&slope1(st[top-1],st[top])<slope1(st[top],i))top--; st[++top]=i; } for(int i=r,j=0;i>=mid+1;i--){ while(j<top&&slope1(st[j],st[j+1])>i)j++; now=max(now,f[st[j]]+1ll*(i-st[j])*(i-st[j]+1)/2-pre[i]+pre[st[j]]+g[i+1]),h[i]=max(h[i],now); } now=-0x3f3f3f3f3f3f3f3f,top=0,st[top]=r+1; for(int i=r;i>=mid+1;i--){ while(top>=1&&slope2(st[top-1],st[top])<slope2(st[top],i))top--; st[++top]=i; } for(int i=l,j=0;i<=mid;i++){ while(j<top&&slope2(st[j],st[j+1])>-i)j++; now=max(now,f[i-1]+1ll*(st[j]-i)*(st[j]-i+1)/2-suf[i]+suf[st[j]]+g[st[j]]),h[i]=max(h[i],now); } } int main(){ in(n); for(int i=1;i<=n;i++)in(a[i]); reverse(a+1,a+n+1),calc(n,a),reverse(f+1,f+n+1),memcpy(g,f,sizeof(g)),reverse(a+1,a+n+1),calc(n,a); for(int i=1;i<=n;i++)pre[i]=pre[i-1]+a[i]; for(int i=n;i>=1;i--)suf[i]=suf[i+1]+a[i]; solve(1,n),in(m); for(int i=1,x;i<=m;i++)in(x,y),cout<<max(f[x-1]+g[x+1],h[x]+a[x]-y)<<'\n'; return 0; } ```