Bzoj3203: [Sdoi2013]保护出题人 凸包 + 三分

题目描述

出题人铭铭认为给SDOI2012出题太可怕了,因为总要被骂,于是他又给SDOI2013出题了。

参加SDOI2012的小朋友们释放出大量的僵尸,企图攻击铭铭的家。而你作为SDOI2013的参赛者,你需要保护出题人铭铭。

僵尸从唯一一条笔直道路接近,你们需要在铭铭的房门前放置植物攻击僵尸,避免僵尸碰到房子。

第一关,一只血量为 a1a_1a1 点的墦尸从距离房子 x1x_1x1 米处速接近,你们放置了攻击力为 y1y_1y1 点/秒的植物进行防御;第二关,在上一关基础上,僵尸队列排头增加一只血量为 a2a_2a2 点的僵尸,与后一只僵尸距离 ddd 米,从距离房 x2x_2x2 米处匀速接近,你们重新放置攻击力为 y2y_2y2 点/秒的植物;……;第 nnn 关,僵尸队列共有 nnn 只僵尸,相邻两只僵尸距离 ddd 米,排头僵尸血量为 ana_nan 点,排第二的 僵尸血量 an−1a_{n-1}an1 ,以此类推,排头僵尸从距离房子 xnx_nxn​ 米处匀速接近,其余僵尸跟随排头同时接近,你们重新放置攻击力为 yny_nyn​ 点/秒的植物。

每只僵尸直线移动速度均为 111 米/秒,由于植物射击速度远大于僵尸移动速度,可忽略植物子弹在空中的时间。所有僵尸同时出现并接近,因此当一只僵尸死亡后,下一只僵尸立刻开始受到植物子弹的伤害。

游戏得分取决于你们放置的植物攻击力的总和 ∑i=1nyisum limits _{i=1} ^{n} y_ii=1nyi ,和越小分数越高,为了追求分数上界,你们每关都要放置攻击力尽量小的植物。

作为SDOI2013的参赛选手,你们能保护出题人么?

输入输出格式

输入格式:

第一行两个空格隔开的正整数n和d,分别表示关数和相邻僵尸间的距离。

接下来n行每行两个空格隔开的正整数,第i + 1行为Ai和 Xi,分别表示相比上一关在僵尸队列排头增加血量为Ai 点的僵尸,排头僵尸从距离房子Xi米处开始接近。

输出格式:

一个数,n关植物攻击力的最小总和 ,保留到整数。

输入输出样例

输入样例#1: 
5  2
3  3
1  1
10 8
4  8
2  3
输出样例#1: 
7

说明

第一关:距离房子3米处有一只血量3点的僵尸,植物最小攻击力为1.00000;
第二关:距离房子1米处有一只血量1点的僵尸、3米处有血量3点的僵尸,植物最小攻击力为1.33333;
第三关:距离房子8米处有一只血量10点的僵尸、10米处有血量1点的僵尸、12米处有血量3点的僵尸,植物最小攻击力为1.25000;
第四关:距离房子8米处有一只血量4点的僵尸、10米处有血量10点的僵尸、12米处有血量1点的僵尸、14米处有血量3点的僵尸,植物最小攻击力为1.40000;
第五关:距离房子3米处有一只血量2点的僵尸、5米处有血量4点的僵尸、7米处有 血量10点的僵尸、9米处有血量1点的僵尸、11米处有血量3点的僵尸,植物最小攻击力 为2.28571。
植物攻击力的最小总和为7.26905。

对于100%的数据, 1<=n<=10^5,1<=d<=10^12,1<=x<= 10^12,1<=a<=10^12

提交地址 : bzoj3203Luogu3299

题面看的一脸懵逼, 一副不可做的样子;

因为当时翻看各种题解并没有懂, 所以决定写一篇较为详细的题解, 让不会的小伙伴们更快的理解, 少走弯路;

题面不解释, 玩过植物大战僵尸的都理解(没有玩过现在去);

由简单的分析, 我们可以得到, f[i] = max((sum[i] - sum[j-1]) / (d *(i - j) + x[i])), f[i]表示第i关的最小需要的攻击力;

很清晰? 就是j到i的所有僵尸的生命值之和, 除以第j个僵尸到你的距离, 结果不就是攻击力嘛, 最后求max(因为对于每个僵尸j都不能让他走到你跟前), 美滋滋;

O(n^2)妥妥的50分, 但问题来了, 我们可是要AK的人;

仔细看看刚才的方程, 你想到了什么? 斜率!!!

我们令X1 = d * i + x[i], Y1 = sum[i], Y2 = sum[j-1], X2 = d *j;

 

我们可以用单调栈维护一个右下凸壳,维护(sum[i-1]  -  sum[j-1])  /  (d * i - d * j), 为什么要这么维护?

我们考虑之前的式子, 我们的i在求它的时候是不变的, 所以我们把i想象成一个定点, 令i的坐标为(d * i + x[i], sum[i]);

然后我们加入点i, 我们要求的攻击力其实就是i和当前凸包上的点的斜率最大值;

因为sum[i](点i的纵坐标) - sum[j-1](凸包上维护的点的纵坐标) = sum[i] - sum[j-1](要求的式子的上半边);

d * i + x[i](点i的横坐标) - d *j (凸包上维护的点的横坐标) = d *(i - j) + x[i](要求的式子的下半边);

刚好满足!!!!

 哇塞我们好像有O(n)的方法了!!

然而是ma?

我们好像还要求点i与凸包上的点的斜率最大值!

哦...这不是熟悉的三分法求凸函数极值的变式嘛!

所以加上用三分, 总复杂度O(nlogn?);

稳!!!

对了这道题要用long long, 别怪我没提醒你, 要不打死都想不出来

最后奉上代码(码风奇特切勿吐槽)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cctype>
#include <cmath>
using namespace std;
#define int long long

inline char nc()
{
    static const int BS = 1 << 22;
    static unsigned char buf[BS], *st, *ed;
    if (st == ed) ed = buf + fread(st = buf, 1, BS, stdin);
    return st == ed ? EOF : *st++;
}
#define nc getchar
inline int read()
{
    int ch = nc();
    int res = 0;
    bool flag = 0;
    while (!isdigit(ch)) 
    {
        if (ch == '-') flag = 1;
        ch = nc();
    }
    while (isdigit(ch))
    {
        res = (res << 3) + (res << 1) + (ch - '0');
        ch = nc();
    }
    return flag ? -res : res;
}

const int N = 1e5+5;
#define eps 1e-6
#define lldou long double

int n, d;
int dis[N], sum[N];

inline int X1(int i)
{
    return d * i + dis[i];
}

inline int Y1(int i)
{
    return sum[i];
}

inline int X2(int i)
{
    return d * i;
}

inline int Y2(int i)
{
    return sum[i-1];
}

inline long double slope1(int i, int j)
{
    return (double)((double)(Y2(i) - Y2(j)) / (double)(X2(i) - X2(j)));/* - dis[i]*/
}

inline long double slope(int i, int j)
{
    return (double)((double)(Y1(i) - Y2(j)) / (double)(X1(i) - X2(j)));
}

int sta[N], top; // 单调栈 

signed main()
{
    n = read(), d = read();
    
    for (register int i = 1 ; i <= n ; i ++)
    {
        int x = read();
        sum[i] = sum[i-1] + x;
        dis[i] = read();
    }
    
    lldou ans = 0.0;
    
    for (register int i = 1 ; i <= n ; i ++)
    {
        while (top > 1 and slope1(i, sta[top]) <= slope1(sta[top], sta[top-1])) top--;
        sta[++top] = i;
        int l = 1, r = top, mid1, mid2;
        while (l < r)
        {
            mid1 = (l + r + l) / 3;
            mid2 = (r + r + l) / 3;
            if (mid1 == mid2) break;
            if (slope(i, sta[mid1]) > slope(i, sta[mid2])) r = mid2 - 1;
            else l = mid1 + 1;
        }
        ans += (lldou)max(slope(i, sta[l]), slope(i, sta[r]));
    //    printf("%Lf
", (lldou)max(slope(i, sta[l]), slope(i, sta[r])));
    }
    
    printf("%.0Lf
", ans);
    return 0;
}

 转载请注明出处谢谢谢谢

原文地址:https://www.cnblogs.com/BriMon/p/9079365.html