题意
给出一个长为 \(n\) 的序列,让你选择一段长度 \(\ge 2\) 区间内所有值变为区间右或左端点的值,最大化操作后的权值和。
思路
以第一问为例,选择一段区间 \((l,r]\) 后权值和的变化量为:
\[(r-l)\times a_r-(sum_r-sum_l)
\]
其中 \(sum_i=\sum\limits_{j=1}^{i}a_j\)。把式子拆开,发现是如下的形式:
\[r\times a_r-sum_r-l\times a_r+sum_l
\]
当 \(r\) 固定时,这是一个关于 \(l\) 的一次函数。不妨设 \(y=-a_r\times l+sum_l\),那么要最大化原式的值,就是要最大化这条直线的斜率。于是套路地在凸包上三分即可。
第二问把第一问的数组翻转再跑一遍即可。
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
constexpr int N=3e5+5;
int n,a[N];
ll sum[N];
pair<int,ll>operator-(const pair<int,ll>&x,const pair<int,ll>&y){ // 计算x->y的向量return {x.first-y.first,x.second-y.second};
}
ll operator*(const pair<int,ll>&x,const pair<int,ll>&y){ // 计算向量叉积return x.first*y.second-x.second*y.first;
}
struct ConvexHull{ // 凸包vector<pair<int,ll>>st;void clear(){st.clear();}void insert(pair<int,ll>x){while(st.size()>=2&&(st[st.size()-2]-st.back())*(x-st.back())<=0)st.pop_back();st.push_back(x);}ll query(ll k){assert(st.size());if(st.size()==1)return k*st[0].first+st[0].second;int l=0,r=st.size()-1;while(r-l>1){ // 三分const int mid=(l+r)>>1;if(st[mid-1].first*k+st[mid-1].second<st[mid+1].first*k+st[mid+1].second)l=mid;else r=mid;}return max(k*st[l].first+st[l].second,k*st[r].first+st[r].second);}
}ch;
signed main(){ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];ch.clear();ch.insert({0,0});ll ans=-1e18;for(int i=2;i<=n;i++){ // 注意区间左右端点不能相同,所以从2开始ans=max(ans,ch.query(-a[i])-sum[i]+1ll*i*a[i]);ch.insert({i-1,sum[i-1]});}cout<<sum[n]+ans<<'\n';reverse(a+1,a+n+1);for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];ch.clear();ch.insert({0,0});ans=-1e18;for(int i=2;i<=n;i++){ans=max(ans,ch.query(-a[i])-sum[i]+1ll*i*a[i]);ch.insert({i-1,sum[i-1]});}cout<<sum[n]+ans<<'\n';return 0;
}