• 详细讲解Codeforces Round #625 (Div. 2)E


    题意:有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
    
  • 相关阅读:
    V4L2 soccamera 子系统
    ubuntu10.04 vim 配置
    Video for Linux Two
    I2C总线的仲裁机制
    Android Camera 通过V4L2与kernel driver的完整交互过程
    ubuntu安装辞典
    v4l2 camera 驱动架构 之 isp controller 驱动
    Android Camera 运行流程
    CentOS 7.x安装图文示范
    同余与模运算
  • 原文地址:https://www.cnblogs.com/zsh-notes/p/12421228.html
Copyright © 2020-2023  润新知