详细讲解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
2 3 3 2 3 4 7 2 4 3 2 5 11 1 2 4 2 1 6 3 4 6
1