E. Monsters (hard version)
Posted onlyblues
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了E. Monsters (hard version)相关的知识,希望对你有一定的参考价值。
E. Monsters (hard version)
This is the hard version of the problem. In this version, you need to find the answer for every prefix of the monster array.
In a computer game, you are fighting against $n$ monsters. Monster number $i$ has $a_i$ health points, all $a_i$ are integers. A monster is alive while it has at least $1$ health point.
You can cast spells of two types:
- Deal $1$ damage to any single alive monster of your choice.
- Deal $1$ damage to all alive monsters. If at least one monster dies (ends up with $0$ health points) as a result of this action, then repeat it (and keep repeating while at least one monster dies every time).
Dealing $1$ damage to a monster reduces its health by $1$.
Spells of type 1 can be cast any number of times, while a spell of type 2 can be cast at most once during the game.
For every $k = 1, 2, \\ldots, n$, answer the following question. Suppose that only the first $k$ monsters, with numbers $1, 2, \\ldots, k$, are present in the game. What is the smallest number of times you need to cast spells of type 1 to kill all $k$ monsters?
Input
Each test contains multiple test cases. The first line contains the number of test cases $t$ ($1 \\le t \\le 10^4$). The description of the test cases follows.
Each test case consists of two lines. The first line contains a single integer $n$ ($1 \\le n \\le 2 \\cdot 10^5$) — the number of monsters.
The second line contains $n$ integers $a_1, a_2, \\ldots, a_n$ ($1 \\le a_i \\le n$) — monsters\' health points.
It is guaranteed that the sum of $n$ over all test cases does not exceed $2 \\cdot 10^5$.
Output
For each test case, print $n$ integers. The $k$-th of these integers must be equal to the smallest number of times you need to cast spells of type 1 to kill all $k$ monsters, if only monsters with numbers $1, 2, \\ldots, k$ are present in the game.
Example
input
2 3 3 1 2 6 4 1 5 4 1 1
output
2 1 0 3 2 4 4 4 4
Note
In the first test case, for $k = n$, the initial health points of the monsters are $[3, 1, 2]$. It is enough to cast a spell of type 2:
- Monsters\' health points change to $[2, 0, 1]$. Since monster number $2$ dies, the spell is repeated.
- Monsters\' health points change to $[1, 0, 0]$. Since monster number $3$ dies, the spell is repeated.
- Monsters\' health points change to $[0, 0, 0]$. Since monster number $1$ dies, the spell is repeated.
- Monsters\' health points change to $[0, 0, 0]$.
Since it is possible to use no spells of type 1 at all, the answer is $0$.
In the second test case, for $k = n$, the initial health points of the monsters are $[4, 1, 5, 4, 1, 1]$. Here is one of the optimal action sequences:
- Using a spell of type 1, deal $1$ damage to monster number $1$. Monsters\' health points change to $[3, 1, 5, 4, 1, 1]$.
- Using a spell of type 1, deal $1$ damage to monster number $4$. Monsters\' health points change to $[3, 1, 5, 3, 1, 1]$.
- Using a spell of type 1, deal $1$ damage to monster number $4$ again. Monsters\' health points change to $[3, 1, 5, 2, 1, 1]$.
- Use a spell of type 2:
- Monsters\' health points change to $[2, 0, 4, 1, 0, 0]$. Since monsters number $2$, $5$, and $6$ die, the spell is repeated.
- Monsters\' health points change to $[1, 0, 3, 0, 0, 0]$. Since monster number $4$ dies, the spell is repeated.
- Monsters\' health points change to $[0, 0, 2, 0, 0, 0]$. Since monster number $1$ dies, the spell is repeated.
- Monsters\' health points change to $[0, 0, 1, 0, 0, 0]$.
- Using a spell of type 1, deal $1$ damage to monster number $3$. Monsters\' health points change to $[0, 0, 0, 0, 0, 0]$.
Spells of type 1 are cast $4$ times in total. It can be shown that this is the smallest possible number.
解题思路
先说简单版C. Monsters (easy version)的做法。
在所有的选择方案中,对于在第二种攻击之后存在若干次第一种攻击的方案,我们总是可以把剩下的第一种攻击提前到第二种攻击之前。对于第二次攻击结束后还剩下血量的怪物,只能使用第一种攻击将其变成$0$,而如果我们在第二种攻击之前提前对这些还剩血量的怪物使用剩下的第一种攻击,由于第二种攻击对所有怪兽造成的伤害不变,所以在第二种攻击后这些怪物的血量就会变成$0$,并且第一种攻击的次数不会变多。
所以现在我们要如何安排第一次攻击,使得最后的第二种攻击能够一次性消灭所有的怪物?由于攻击与怪物的排列顺序无关,因此假设现在数组$a$(怪物血量)是升序排序的,通过若干次的第一种攻击后变成数组$a\'$(依然升序)。很明显如果想让第二种攻击一次性将数组清零,那么$a\'$中任意相邻的两个元素的差值不能超过$1$,因为如果存在某个相邻的元素$a_i^\'-a_i-1^\' \\geq 2$(假设是满足这个条件的最小的$i$),那么当$i-1$被消灭,$i$前面的怪兽血量都已经清零,并且剩下的怪兽($i$之后)都已经损失了$a_i-1^\'$的血量,此时$i$是所有剩下的怪物中血量最小的,即还剩下$a_i^\'-a_i-1^\'$的血量,但这个值严格大于$1$,因此第二种攻击被中断,从而无法一次性消灭所有的怪物。
因此$a\'$中任意两个元素之间的差值不能超过$1$,可以等于$1$也可以等于$0$。这就启示我们可以遍历$a$数组($a$数组已升序排序),对于$a$中的第$i$个元素,考虑$a\'$中的第$i-1$个元素,如果$a_i - a_i-1^\' > 1$(假设$a_0^\' = 0$),那么就要对$a_i$至少进行$a_i - (a_i-1^\' + 1)$次第一种攻击,根据贪心思想我们就取$a_i - (a_i-1^\' + 1)$,并且有$a_i^\' = a_i-1 + 1$。
C. Monsters (easy version)的AC代码如下:
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 2e5 + 10; int a[N]; void solve() int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); sort(a + 1, a + n + 1); LL ret = 0; for (int i = 1; i <= n; i++) if (a[i] != a[i - 1]) ret += a[i] - a[i - 1] - 1; a[i] = a[i - 1] + 1; printf("%lld\\n", ret); int main() int t; scanf("%d", &t); while (t--) solve(); return 0;
接下来是hard版本的,其实还是有用到easy版的思想的。hard版本就是求数组的每一个前缀使用第一种攻击的最少次数是多少(easy版就是求整个数组使用的第一种攻击最少是多少)。
在easy版本中$a\'$数组可能会存在重复元素,比如有$a = [1,1,3,3,3,5]$,根据上面的算法得到的$a\' = [1,1,2,3,3,4]$,就会有重复的数字$1$和$3$,那么最小代价就是$(1-1)+(1-1)+(3-2)+(3-3)+(3-3)+(5-4) = (1+1+3+3+3+5)-(1+1+2+3+3+4) = 2$。即最小代价为$\\sum\\limits_i=1^na_i - \\sum\\limits_i=1^na_i^\'$。
而如果$a\'$中没有重复的元素出现,那么$a\'$必然是$1 \\sim n$的形式(即$a_i^\' = i$),那么最小代价就是$\\sum\\limits_i=1^na_i - \\fracn(n+1)2$。因此如果知道从$a$得到$a\'$没有重复元素,那么代价就可以通过公式直接求出来而无需遍历$a$来求$a\'$。
之所以要说这个是因为$a\'$中从第$2$个开始的重复元素是不会产生代价的,这意味着对应的$a_i = a_i^\'$。这是因为$a\'$中相邻两个重复元素的差值为$0$,而根据构造$a\'$的贪心原则(只要$a_i - a_i-1^\' > 1$,那么$a_i^\' = a_i-1^\' + 1$),两个相邻元素的差值最大为$1$,而只有相邻元素相同即$a_i = a_i-1^\'$,才会有$a_i\' = a_i-1^\' = a_i$。因此在$a$中,把$a\'$中重复的元素所对应的数删除,并不会影响最小代价的结果。
例如$a = [1,1,3,3,3,5]$,$a\' = [1,1,2,3,3,4]$,$a_2^\' = 1$和$a_5^\' = 3$都是重复元素,那么在$a$中把对应的下标删除得到$a = [1,3,3,5]$,有$a\' = [1,2,3,4]$,代价为$(1+3+3+5)-(1+2+3+4) = 2$。
因此为了求每个前缀的最小代价,每次加入一个新的元素,我们需要维护一个没有重复元素的$a\'$以及对应的$a$,这就是解决这题的主要思想。可是关键的地方并不是求$a\'$,而是判断每次加入的元素是否会使得$a\'$出现重复的元素,以及需要在$a$中删除哪个元素来保证$a\'$没有重复的元素出现。前面之所以会讲得这么啰嗦是为了方便读者看得懂下面要说明的解决这个问题的方法,因为我第一次看别人的题解看不懂所以希望记录的详细些。
已知$a = [1,3,3,5]$和$a = [1,1,3,3,3,5]$的代价是一样的,那么$a = [1,3,3,3,3,3,5]$呢?,实际上还是一样的,画个值域图:
其实可以发现,只要$a$中数值为$3$的个数超过$2$个,不管加入再多的$3$(即红色部分)都不会改变代价,也就是说这些多出的$3$是应该被删除的。因为$a\'$中相邻的两个数相差最多为$1$,因此在值域不超过$3$的所有数里(即绿色框内的数),只要数的个数超过$3$那么必然至少存在相邻两个数的差值为$0$,即存在值相同的数。更一般的,如果数值小于等于$x$的数的个数超过$x$个,那么总是可以删除多余的数并使得代价不变。
再举例子,红色的数表示可以删除的数(任何不超过$x$的数都不多于$x$个):
假设数值为$i$的数有$c_i$个,对$c$求前缀和那么就得到$s_i = \\sum\\limits_j=1^ic_j$,表示所有数值不超过$i$的数的个数。因此我们要保证在$a$的每个前缀中,对于所有的$i$(这里的$i$是数值不是下标)都有$s_i \\leq i$,即保证任何不超过$i$的数都不多于$i$个,这样才能保证$a\'$中不会有重复的数出现,才能用公式$\\sum\\limits_i=1^ma_i - \\fracm(m+1)2$直接求出前缀的最小代价。
为此,每次加入一个数$x$,我们都要对$s_x \\sim s_n$加上$1$(题目中值域的最大值为$n$),然后检查是否存在某个值$s_y > y$(因为每次只增加一个数,因此如果不满足$s_y \\leq y$,那么必然就是$s_y = y + 1$),如果存在则找到满足这个情况的最小的值$y\'$,然后删除$y\'$,最后代入公式求答案即可。
解释一下为什么要找到最小的$y\'$,因为删除$y\'$后,在后面比$y\'$大的数中如果存在$s_x > x$的都会变成$s_x \\leq x$(因为减少了$1$),参考下图:
因此对于$a$的每个前缀,都需要给一个值域区间加上$1$,同时还需要判断值域$1 \\sim n$是否都满足$s_i \\leq i$的情况。然后$s_i \\leq i$其实可以等价成为$s_i - i \\leq 0$,因此我们可以用线段树来维护每一个$s_i - i$。值域区间加上$1$就相当于给这个区间的$s_i - i$都加上$1$,然后判断是否存在$s_i - i > 0$并找到满足这个条件最小的值就相当于判断区间的最大值是否大于$0$,并通过二分找到最左边满足大于$0$的那个数。这些都可以用线段树来实现,线段树只需要维护区间的最大值即可。
AC代码如下:
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 2e5 + 10; int a[N]; struct Node int l, r, maxv, add; tr[N * 4]; void build(int u, int l, int r) if (l == r) tr[u] = l, r, -l, 0; else int mid = l + r >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); tr[u] = l, r, max(tr[u << 1].maxv, tr[u << 1 | 1].maxv), 0; void pushdown(int u) if (tr[u].add) tr[u << 1].add += tr[u].add; tr[u << 1].maxv += tr[u].add; tr[u << 1 | 1].add += tr[u].add; tr[u << 1 | 1].maxv += tr[u].add; tr[u].add = 0; void modify(int u, int l, int r, int c) if (tr[u].l >= l && tr[u].r <= r) tr[u].maxv += c; tr[u].add += c; else pushdown(u); int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) modify(u << 1, l, r, c); if (r >= mid + 1) modify(u << 1 | 1, l, r, c); tr[u].maxv = max(tr[u << 1].maxv, tr[u << 1 | 1].maxv); int query(int u) if (tr[u].l == tr[u].r) return tr[u].l; pushdown(u); if (tr[u << 1].maxv > 0) return query(u << 1); // 左边存在大于0的值,那么取左边找 return query(u << 1 | 1); // 大于0的值在右边 void solve() int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); build(1, 1, n); int cnt = 0; // 当前维护的数的个数 LL s = 0; // 当前维护的数的总和 for (int i = 1; i <= n; i++) cnt++, s += a[i]; // 先记录 modify(1, a[i], n, 1); // a[i]的个数加1,si ~ sn都加1,si-i ~ sn-n都加1 if (tr[1].maxv > 0) // 存在si-i大于0的数,需要删除最小的那个数 int x = query(1); // 找到最小的那个数 modify(1, x, n, -1); // 删除这个数,区间减1 cnt--, s -= x; // 删除最小的那个数 printf("%lld ", s - cnt * (cnt + 1ll) / 2); // 公式计算答案 printf("\\n"); int main() int t; scanf("%d", &t); while (t--) solve(); return 0;
参考资料
Codeforces Round #850 (Div. 2) E(二分线段树):https://zhuanlan.zhihu.com/p/603794263
By MilimNava, contest: Codeforces Round #850 (Div. 1, based on VK Cup 2022 - Final Round), problem: (C) Monsters (hard version):https://codeforces.com/contest/1785/submission/192299527
以上是关于E. Monsters (hard version)的主要内容,如果未能解决你的问题,请参考以下文章
Tree Traversals - Hard Version
Coffee and Coursework (Hard Version)
11-散列4 Hashing - Hard Version (30 分)
D2. RGB Substring (hard version)||D1. RGB Substring (easy version)