[小A与最大子段和][斜率优化dp+二分]

时间:2023-03-09 05:56:08
[小A与最大子段和][斜率优化dp+二分]

链接:https://ac.nowcoder.com/acm/contest/545/A
来源:牛客网
题目描述

小A在网上看到了 "最大子段和" 问题的解法.第二天,小A向小B讲解了这个问题.
但小B抛出了一个疑问:如果让每个数的权值是它本身的值乘上它在子段中的位置呢?
小A被难住了,你能帮他解决这个问题吗?
形式化地说,你需要在一个序列 a  里找到一个非空子段(子段是连续的) b, 满足 ∑mi=1b[i]×i∑i=1mb[i]×i 最大( m 是 b 的长度)
输入描述:
第一行一个整数 n ,表示序列的长度
第二行 n 个整数,第 i 个数表示 aiai
输出描述:
一行一个整数,表示最大的 ∑mi=1b[i]×i∑i=1mb[i]×i 
示例1
输入
4
-100 1 2 3
输出
14

说明
1×1+2×2+3×3=141×1+2×2+3×3=14

备注:
1≤n≤2×10^5
0≤∣ai|≤2×10^3
题解:枚举每个区间右端点,如果再枚举左端点则复杂度为O(N^2)不可行。使用斜率优化。 令s为前缀和,p[i] = ∑(i * s[i]),区间[j, i]以i为右端点时区间和可以表示为p[i] - p[j - 1] - (j - 1) * (s[i] - s[j - 1])。 斜率方程(k < j < i) j比k优,((j * s[j] - p[j]) - (k * s[k] - p[k])) / (j - k) > s[i],由于前缀和s不单调所以不能直接将队首节点弹出,(即只通过插入当前点更新队尾元素,不能丢弃队首元素,【而如果前缀和s单调,易知队首元素在当前不满足时也不会满足以后的点即可以丢弃】)
所以就不能通过丢弃队首来找满足条件的点了,只能通过二分斜率凸包来找满足条件的斜率。
 #include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<queue>
#include<time.h>
using namespace std;
typedef long long ll;
const int N = 2e5+;
ll a[N],A[N],B[N],dp[N];
struct pot{
int x;
ll y;
}que[N];
int head=;
int tail=;
bool check(int s,int k){
if(tail<)return true;
return que[s].y-que[s-].y<(que[s].x-que[s-].x)*B[k];
}
int main(){
int n;
scanf("%d",&n);
for(int i=;i<=n;i++){scanf("%lld",&a[i]);dp[i]=a[i];}
for(int i=;i<=n;i++){
A[i]=i*a[i]+A[i-];
B[i]=a[i]+B[i-];
}
ll ans=-;
for(int i=;i<=n;i++){
int l=head;
int r=tail;
int m=l;
while(l<=r){
int mid=(l+r)/;
if(check(mid,i)){
m=mid;
r=mid-;
}
else l=mid+;
}
dp[i]=max(A[i]-A[que[m-].x]-que[m-].x*(B[i]-B[que[m-].x]),dp[i]);
ans=max(ans,dp[i]);
while(tail>=&&(i*B[i]-A[i]-(que[tail].y))*(que[tail].x-que[tail-].x)>(i-que[tail].x)*((que[tail].y-que[tail-].y)))tail--;
que[++tail].x=i;
que[tail].y=i*B[i]-A[i];
}
cout<<ans<<endl;
return ;
}