[BZOJ3203][Sdoi2013]保护出题人(凸包+三分)

先考虑暴力怎么做。
Si S i 表示前 i i 个僵尸(按照输入顺序)的血量之和。
考虑如何判断在第i关,植物攻击力为 mid m i d 是否可行。
容易得出,消灭前 i i 个僵尸(排头兵为第1个)需要 SiSj1mid S i − S j − 1 m i d 的时间,只要保证在这段时间内僵尸不进房子即可。
也就是说,植物攻击力为 mid m i d 时,可行的条件是:
对于每一个 1ji 1 ≤ j ≤ i ,都有:

SiSj1midXi+d×(ij) S i − S j − 1 m i d ≤ X i + d × ( i − j )

所以,第 i i 关植物的最小攻击力为:
max1ji{SiSj1Xi+d×id×j}

这样是 O(n2) O ( n 2 ) 的。但可以发现上式是一个斜率方程,
表示 (d×j,Sj1) ( d × j , S j − 1 ) (Xi+d×i,Si) ( X i + d × i , S i ) 之间的斜率。
因此将第 i i 个僵尸看作一个点(d×i,Si1),维护前 i i 个点构成的下凸壳。
可以看出,最大值的点一定在下凸壳上。
又由于(Xi+d×i,Si)这一点到下凸壳上点的斜率是单峰的,因此可以三分查找最大值。
(其实就是求下凸壳过点 (Xi+d×i,Si) ( X i + d × i , S i ) 的切线斜率)
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n, top, stk[N];
ll d, sum[N], X[N], X_[N], Y_[N];
double ans;
double slope(ll X1, ll Y1, ll X2, ll Y2)
{
    return 1.0 * (Y2 - Y1) / (X2 - X1);
}
bool check(int p1, int p2, int p3)
{
    return slope(X_[p1], Y_[p1], X_[p2], Y_[p2]) >
        slope(X_[p2], Y_[p2], X_[p3], Y_[p3]);
}
int main()
{
    int i;
    scanf("%d%lld", &n, &d);
    For (i, 1, n)
    {
        scanf("%lld%lld", &sum[i], &X[i]);
        sum[i] += sum[i - 1];
        X_[i] = d * i; Y_[i] = sum[i - 1];
        while (top > 1 && check(stk[top - 1], stk[top], i))
            top--;
        stk[++top] = i;
        ll x = X[i] + d * i, y = sum[i];
        int l = 1, r = top;
        while (l != r)
        {
            int cm = (r - l + 1) / 3;
            int mid1 = l + cm - 1, mid2 = l + (cm << 1) - 1;
            if (mid1 == mid2) break;
            if (slope(x, y, X_[stk[mid1]], Y_[stk[mid1]]) <
                slope(x, y, X_[stk[mid2]], Y_[stk[mid2]]))
                    l = mid1 + 1;
            else r = mid2 - 1;
        }
        ans += max(slope(x, y, X_[stk[l]], Y_[stk[l]]),
            slope(x, y, X_[stk[r]], Y_[stk[r]]));
    }
    printf("%.0lf\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值