BZOJ5380: Function 单调栈维护凸壳

Description
小B有一个序列A
给多个询问,每次给一个起始点,一个操作步数,他从这个起始点出发。
每轮操作他先选择当前位置的数,然后它有两种选择呆在当前位置或去往当前位置-1的位置。
每次询问他得到的最小值。


Sample Input
6
2 2 3 4 3 4
4
4 5
3 4
3 4
2 3


Sample Output
12
9
9
5


首先得到一个策略肯定是一直往i-1走,然后一直呆在一个位置。
设当前询问为x,y。
得一个答案式:ans=min(s[y]-s[i]+a[i]*(x-y+i))(1<=i<=y)
然后可化成这样:ans=min(s[y]+a[i]*(x-y)-s[i]+i*a[i])(1<=i<=y)
然后你发现a[i]*(x-y)-s[i]+i*a[i]是个直线方程。
因为对于所有i都要加上s[y]于是s[y]的影响可以消去。
那你接下来不就单调栈一下变成一个凸壳,然后二分即可。
这里要注意的是由于斜率并不保证单调递增,那你要想想。
假设对于一个i有j,k两个数可造成贡献,设j < k,a[j]>=a[k]且j~k的数>=a[k]
于是你就可以维护一个斜率单调递增的队列,那就偷税。。。
好久没出来做题,感觉码力极弱。。。


#include <cstdio>
#include <cstring>
#include <algorithm>

#define eps 1e-5
using namespace std;
typedef long long LL;
int read() {
    int s = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * f;
}

struct node {
    int x, y, opt;
} q[510000];
int tp, sta[510000];
double tmp[510000];
int a[510000];
LL gg[510000], s[510000], hh[510000];

bool cmp(node a, node b) {return a.y < b.y;}

bool sl(int i, int j, int k) {
    double x = (double)(gg[i] - gg[k]) / (a[k] - a[i]);
    double y = (double)x * a[i] + gg[i];
    double y2 = (double)x * a[j] + gg[j];
    if(y2 - y >= eps) return 1;
    else return 0;
}

int main() {
    int n = read();
    for(int i = 1; i <= n; i++) a[i] = read(), s[i] = s[i - 1] + a[i], gg[i] = (LL)a[i] * i - s[i];
    int Q = read();
    for(int i = 1; i <= Q; i++) q[i].x = read(), q[i].y = read(), q[i].opt = i;
    sort(q + 1, q + Q + 1, cmp);
    sta[tp = 1] = 1;
    int now = 0;
    for(int i = 1; i <= Q; i++) {
        while(now < q[i].y) {
            now++;
            while(tp && a[sta[tp]] >= a[now]) tp--;
            while(tp > 1 && sl(sta[tp - 1], sta[tp], now)) tp--;
            sta[++tp] = now;
            if(tp > 1) tmp[tp - 1] = (double)(gg[sta[tp]] - gg[sta[tp - 1]]) / (a[sta[tp - 1]] - a[sta[tp]]);
        } int l = 1, r = tp - 1;
        double uu = q[i].x - q[i].y;
        int ans = 0;
        while(l <= r) {
            int mid = (l + r) / 2;
            if(tmp[mid] - uu >= eps) l = mid + 1, ans = mid;
            else r = mid - 1;
        } ans = sta[ans + 1];
        hh[q[i].opt] = (LL)a[ans] * ans - s[ans] + s[q[i].y] + a[ans] * uu;
    } for(int i = 1; i <= Q; i++) printf("%lld\n", hh[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值