[小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≤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单调,易知队首元素在当前不满足时也不会满足以后的点即可以丢弃】)
所以就不能通过丢弃队首来找满足条件的点了,只能通过二分斜率凸包来找满足条件的斜率。
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstdio> 6 #include<queue> 7 #include<time.h> 8 using namespace std; 9 typedef long long ll; 10 const int N = 2e5+10; 11 ll a[N],A[N],B[N],dp[N]; 12 struct pot{ 13 int x; 14 ll y; 15 }que[N]; 16 int head=1; 17 int tail=0; 18 bool check(int s,int k){ 19 if(tail<2)return true; 20 return que[s].y-que[s-1].y<(que[s].x-que[s-1].x)*B[k]; 21 } 22 int main(){ 23 int n; 24 scanf("%d",&n); 25 for(int i=1;i<=n;i++){scanf("%lld",&a[i]);dp[i]=a[i];} 26 for(int i=1;i<=n;i++){ 27 A[i]=i*a[i]+A[i-1]; 28 B[i]=a[i]+B[i-1]; 29 } 30 ll ans=-10000000; 31 for(int i=1;i<=n;i++){ 32 int l=head; 33 int r=tail; 34 int m=l; 35 while(l<=r){ 36 int mid=(l+r)/2; 37 if(check(mid,i)){ 38 m=mid; 39 r=mid-1; 40 } 41 else l=mid+1; 42 } 43 dp[i]=max(A[i]-A[que[m-1].x]-que[m-1].x*(B[i]-B[que[m-1].x]),dp[i]); 44 ans=max(ans,dp[i]); 45 while(tail>=2&&(i*B[i]-A[i]-(que[tail].y))*(que[tail].x-que[tail-1].x)>(i-que[tail].x)*((que[tail].y-que[tail-1].y)))tail--; 46 que[++tail].x=i; 47 que[tail].y=i*B[i]-A[i]; 48 } 49 cout<<ans<<endl; 50 return 0; 51 }