CodeForces 631E Product Sum(斜率优化DP+二分|三分) ★

题意:给出n个数,现在可以移动一个数的位置,现在要使和sigma(ai*i)最大,询问这个最大和。

思路:将一个数向左移动和向右移动是一样的,现在考虑向左移动。

先预处理出前缀和,将一个数向左移动后,那么改变量为sum[r-1]-sum[l-1]+a[r]*(r-l),考虑枚举r,那么和r有关的数据就变成了常量。

现在问题转化成了求a[r]*l-sum[l-1],注意到这里l和sum[l-]都是递增的,所以可以考虑用斜率优化来加速dp,

维护一个下凸曲线,然后对于每一个a[r],二分斜率或者三分截距就可以解决当前位置左移的最大值。

直接三分:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <vector>
#include <set>
#include <list>
#include <queue>
#include <map>
#include <bitset>
using namespace std;
#define L(i) i<<1
#define R(i) i<<1|1
#define INF  0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-3
#define maxn 200100
#define MOD 1000000007

int n;
long long a[maxn],cur;
long long total,sum[maxn];
long long solve(int x,int y)
{
    if(x <= y)
        return total - sum[y] + sum[x] + a[x] * (y - x);
    return total + sum[x-1] - sum[y-1] + a[x] * (y - x);
}
int main()
{
    int t;
    //scanf("%d",&t);
    while(scanf("%d",&n) != EOF)
    {
        sum[0] = 0;
        for(int i = 1; i <= n; i++)
        {
            scanf("%lld",&a[i]);
            sum[i] = sum[i-1] + a[i];
            total += i * a[i];
        }
        long long ans = -0x3f3f3f3f3f3f3f3f;
        for(int i = 1; i <= n; i++)
        {
            int l = 1,r = n;
            long long cur = -0x3f3f3f3f3f3f3f3f;
            while(l + 1 <= r)
            {
                int mid = l + (r - l) / 3;
                int mmid = r - (r - l) / 3;
                long long ans1 = solve(i,mid);
                long long ans2 = solve(i,mmid);
                if(ans1 > ans2)
                {
                    r = mmid - 1;
                    cur = max(cur,ans1);
                }
                else
                {
                    l = mid + 1;
                    cur = max(cur,ans2);
                }
            }
            cur = max(cur,solve(i,l));
            ans = max(ans,cur);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

斜率优化:

#include<bits/stdc++.h>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
#define pb push_back
#define mp make_pair
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int MAXN = 200020;
const LL INF = 1e17;
int n;
int a[MAXN];
LL sumv[MAXN];
int Q[MAXN], h, t;
struct Point {
	LL x, y;
	Point(LL _x = 0, LL _y = 0) : x(_x), y(_y) {}
} p[MAXN];

LL getY(int k, int j) {
	return p[k].y-p[j].y;
}
LL getX(int k, int j) {
	return p[k].x-p[j].x;
}
bool check1(int k, int j) {
	return getY(Q[k+1], Q[k]) >= getX(Q[k+1], Q[k])*a[j]; 
}
bool check2(int k, int j) {
	return getY(Q[k], Q[k+1]) <= getX(Q[k], Q[k+1])*a[j];
}


int main()
{
    //freopen("input.txt", "r", stdin);
	scanf("%d", &n);
	LL ans = 0;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		sumv[i] = sumv[i-1] + a[i];
		ans += (LL)i * a[i];
	}
	for (int i = 1; i <= n; i++) 
		p[i] = Point(i, sumv[i-1]);
	LL tmp = ans;
	t = 0;
	for (int i = 2; i <= n; i++) {
		while (t > 1 && getY(i-1, Q[t])*getX(Q[t], Q[t-1]) <= getY(Q[t], Q[t-1])*getX(i-1, Q[t]))
			t--;
		Q[++t] = i-1;
		int l = 1, r = t;
		while (l < r) {
			int mid = (l+r) >> 1;
			if (check1(mid, i)) r = mid;
			else l = mid + 1;
		} 
		ans = max(ans, tmp+(LL)a[i]*Q[r]-sumv[Q[r]-1]+sumv[i-1]-(LL)i*a[i]);
	}
	t = 0;
	for (int i = 1; i <= n; i++)
		p[i] = Point(i, sumv[i]);
	for (int i = n-1; i > 0; i--) {
		while (t > 1 && getY(Q[t], i+1)*getX(Q[t-1], Q[t]) >= getY(Q[t-1], Q[t])*getX(Q[t], i+1))
			t--;
		Q[++t] = i+1;
		int l = 1, r = t;
		while (l < r) {
			int mid = (l+r) >> 1;
			if (check2(mid, i))  r = mid;
			else l = mid + 1; 
		} 
		ans = max(ans, tmp+(LL)Q[r]*a[i]-(LL)i*a[i]-sumv[Q[r]]+sumv[i]);
	}
	printf("%I64d", ans);
    return 0;
}



















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值