详细讲解Codeforces Round #625 (Div. 2)E - World of Darkraft: Battle for Azathoth

题意:有n个武器、m套盔甲、p只怪兽,每个武器有攻击力 a 和 花费 ca(硬币) ,每套盔甲有防御力 b 和花费 cb(硬币),每只怪兽有防御力 x,攻击力 y,奖励 z (硬币)。

  你必须选择一个武器和一套盔甲,只有当怪兽的 x < a,y < b 时,你可以杀死它。你可以杀死任意只 能杀死的怪兽,求你最多能赚多少硬币(当你选择最优的武器与盔甲组合时)。

  (即使你杀不死任何一只怪兽,你也必须选择一个武器和一套盔甲。  最终答案为 所有杀死怪兽获得的奖励和  减去  你买武器和盔甲的花费)

题解:首先肯定把武器、盔甲、怪兽都按攻击或防御值排序。n,m,p的数量级相同,我们可以枚举每个武器,考虑在买该武器的前提下,所有盔甲可获得的最大硬币利润。

  1.因为武器和怪兽分别按攻击力a和所需攻击力(防御力x)排序,前面武器能杀死的怪兽,后面的武器一定能杀死,所以枚举每个武器时,怪兽不需要重新遍历,只需从上个武器结束位置继续检查即可,所以怪兽总共只需要遍历一遍,时间复杂度为O(p)。

  2.然后考虑盔甲,我们需要知道使用每个武器时,所有盔甲可获得的最大硬币利润。这是求区间(全局)最值问题,而且每次检查怪兽都需要更新一个区间段(所有防御力b > y的盔甲都要更新),所以自然想到线段树来进行区间更新 和 维护区间最值,总时间复杂度为O((n + p)log(m) )。  (p次更新,n次查询)

  3.因为线段树代码量较大,从“ https://www.cnblogs.com/DeaphetS/p/12393494.html ” 发现了另一种方法: 分块法(分治法)。

    即把m套盔甲分成sqrt(m)块,每块sqrt(m)个,每次更新和求最值都只需要O(根号(m)),全局查询可以简单的与更新同步,时间复杂度为O(p*根号(m)),且结构简单,最终实际运行时间只有436 ms(我的代码运行时间是436ms,原博客说他的运行时间为374ms,可能是使用了pair的原因)。

    (1)m套盔甲按防御力排序,初始化每套盔甲利润为 -cb,用等长数组belong记录每套盔甲所属的块号,记录每一块最大利润的数组mx,整块更新的懒惰标记flag数组。

    (2)更新时,使用upper_bound找到首个严格大于怪兽攻击力y的盔甲编号i,然后更新 i 以后的所有盔甲,即暴力更新 i 所在块的每套盔甲,后面的块都直接整体打懒惰标记直接更新mx。

        若 i 是块首元素,就也可以把 i 所在块也整体处理(打懒惰标记,直接更新mx)

        若 i 不是块首元素,则老老实实地更新 i 所在块内的 i以后的部分元素,然后后面的块直接整体处理。

详细见代码和注释如下:

#include<cstdio>    //scanf,printf
#include<cstring>    //memcpy,memset,strcpy
#include<vector>    //lower_bound, unique
#include<cmath>        //log,acos
#include<algorithm>    //sort
#include<iostream>    //cin,cout
#include<map>
#include<string>    //string
#include<utility>    //pair
#include<queue>
#include<functional>    //greater,less
using namespace std;

const int maxn = 2e5 + 1;
pair<int, int>a[maxn], b[maxn];    //武器和盔甲
struct monster {
    int x, y, z;
}ms[maxn];    //怪兽

bool cmp(monster& a, monster& b) {
    if (a.x == b.x)return a.y < b.y;
    return a.x < b.x;
}
//每套盔甲可获得的最大利润f,所属块数bel;块最大值mx,块懒惰标记flag,块大小B,块数nB
//当前全局最大值cur,本题最终答案ans
int f[maxn], bel[maxn], mx[maxn], flag[maxn], B, nB, cur, ans;
int n, m, p;

void build() {        //建块和初始化
    B = min(m, int(sqrt(m) + 1));
    int i(0);
    cur = ans = -INT_MAX;
    while (i < m) {
        mx[++nB] = -INT_MAX;    //块下标从1开始,与数组默认初始化值0区别开
        for (int j = 0; j < B&&i < m; i++, j++) {
            f[i] = -b[i].second;
            mx[nB] = max(mx[nB], f[i]);
            bel[i] = nB;
        }
        cur = max(cur, mx[nB]);    //不能杀死任一怪兽时也必须买武器和盔甲,可能不更新就直接查全局最值
    }
}

void add(int k) {
    int i = upper_bound(b, b + m, make_pair(ms[k].y, INT_MAX)) - b;    //二分得到比k怪兽防御力大的首个下标
    if (i >= m)return;
    int z = ms[k].z;
    if (i%B != 0) {        //i不是块首元素
        int Bi = bel[i];
        while (bel[i] == Bi) {
            f[i] += z;
            mx[Bi] = max(mx[Bi], f[i] + flag[Bi]);    //记得块内每个元素的实际值还需要加上 块懒惰标记
            i++;
        }
        cur = max(cur, mx[Bi]);
    }
    if (i >= m)return;        //更新完i所在块后,可能后面没有其他块了,而bel默认值为0,会全部更新一遍导致错误
    for (int j = bel[i]; j <= nB; j++) {    //更新后面所有块
        flag[j] += z;
        mx[j] += z;
        cur = max(cur, mx[j]);
    }
}


int main() {
    scanf("%d%d%d", &n, &m, &p);
    for (int i = 0; i < n; i++)scanf("%d%d", &a[i].first, &a[i].second);
    for (int i = 0; i < m; i++)scanf("%d%d", &b[i].first, &b[i].second);
    for (int i = 0; i < p; i++)scanf("%d%d%d", &ms[i].x, &ms[i].y, &ms[i].z);
    sort(a, a + n);
    sort(b, b + m);
    sort(ms, ms + p, cmp);
    build();
    int k(0);
    for (int i = 0; i < n; i++) {
        while (k < p&&ms[k].x < a[i].first)add(k++);    //共p次更新
        ans = max(ans, cur - a[i].second);        //共n次查询,记得减去买武器的花费
    }
    printf("%d", ans);
    return 0;
}

 

Example

Input
2 3 3
2 3
4 7
2 4
3 2
5 11
1 2 4
2 1 6
3 4 6
Output
1
posted @ 2020-03-05 17:00  随~心  阅读(221)  评论(0编辑  收藏  举报