小A與最大子段和(斜率dp + 二分)

題目鏈接:

小A與最大子段和

 

題意:

給定一個序列a[i],找一個a[i]的連續子序列b[i],使得滿足在所有的情況中 \sum_{i=1}^{m}b[i]*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 ,則: 

f[i]-f[j]-j*(sum[i]-sum[j])>f[i]-f[k]-k*(sum[i]-sum[k])

\frac{(j*sum[j]-f[j])-(k*sum[k]-f[k])}{j-k}>sum[i]

左邊式子表示以 i 爲x,以 i*sum[i]-f[i] 爲y的直線的斜率,由於是>sum[i],所以要維護上凸性質

可知:當\frac{(j*sum[j]-f[j])-(k*sum[k]-f[k])}{j-k}>sum[i]>\frac{(i*sum[i]-f[i])-(j*sum[j]-f[j])}{i-j}時,

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;
}