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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值