题目链接:
题意:
给定一个序列a[i],找一个a[i]的连续子序列b[i],使得满足在所有的情况中 最大。(m为b的长度)
思路:
设 dp[i] 表示以第 i 位为结束位置的最优解。
设 f[i] = a[1]*1+a[2]*2+...+a[n]*n 。
设 sum[i] = a[1]+a[2]+...+a[n] 。
设 j 为b序列的起始位置的前一位(即起始位置是 j+1),因此 0<=j<i 。
动态转移方程:dp[i] = max( f[i] - f[j] - j*(sum[i]-sum[j]) ) (0<=j<i) 。
此时复杂度为O(n^2),进行斜率优化:
设 j 点优于 k 点,且 j > k ,则:
左边式子表示以 i 为x,以 i*sum[i]-f[i] 为y的直线的斜率,由于是>sum[i],所以要维护上凸性质。
可知:当时,
j点优于 k 点及之前的所有点 且 优于 i 点及之后的所有点,即 j 点为最优点。因此找第一个斜率小于sum[i]的即为最优点。
由于a[i]中存在负数,所以sum[i]不单调,不能有单调队列找最优点,所以二分查找最优点即可。最后维护一个上凸的性质。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 100;
const ll MOD = 1e9 + 7;
int n;
int a[MAX];
ll f[MAX], sum[MAX];
ll dp[MAX]; // dp[i]=max(f[i]-f[j]-j*(sum[i]-sum[j])) 0<=j<i
ll q[MAX];
ll up(int j, int k) {
return (j * sum[j] - f[j]) - (k * sum[k] - f[k]);
}
ll down(int j, int k) {
return j - k;
}
int bin_search(int l, int r, ll val)
{
int ans = l;
while (l < r) {
int mid = (l + r) >> 1;
if (mid<r&&up(q[mid + 1], q[mid]) > val * down(q[mid + 1], q[mid])) {
ans = mid + 1;
l = mid + 1;
}
else {
r = mid;
}
}
return ans;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + a[i] * i;
sum[i] = sum[i - 1] + a[i];
}
dp[0] = 0;
int left = 1, right = 1;
q[1] = 0;
ll ans = -(1e9 + 7);
for (int i = 1; i <= n; i++) {
int x = bin_search(left, right, sum[i]);
int front = q[x];
dp[i] = f[i] - f[front] - front * (sum[i] - sum[front]);
while (left < right&&up(q[right], q[right - 1])*down(i, q[right]) <= down(q[right], q[right - 1])*up(i, q[right]))
right--;
q[++right] = i;
ans = max(ans, dp[i]);
}
printf("%lld\n", ans);
return 0;
}