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 ai health points, all ai 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,,n, answer the following question. Suppose that only the first k monsters, with numbers 1,2,,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 (1t104). The description of the test cases follows.

Each test case consists of two lines. The first line contains a single integer n (1n2105) — the number of monsters.

The second line contains n integers a1,a2,,an (1ain) — monsters' health points.

It is guaranteed that the sum of n over all test cases does not exceed 2105.

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,,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,因为如果存在某个相邻的元素aiai12(假设是满足这个条件的最小的i),那么当i1被消灭,i前面的怪兽血量都已经清零,并且剩下的怪兽(i之后)都已经损失了ai1的血量,此时i是所有剩下的怪物中血量最小的,即还剩下aiai1的血量,但这个值严格大于1,因此第二种攻击被中断,从而无法一次性消灭所有的怪物。

  因此a中任意两个元素之间的差值不能超过1,可以等于1也可以等于0。这就启示我们可以遍历a数组(a数组已升序排序),对于a中的第i个元素,考虑a中的第i1个元素,如果aiai1>1(假设a0=0),那么就要对ai至少进行ai(ai1+1)次第一种攻击,根据贪心思想我们就取ai(ai1+1),并且有ai=ai1+1

  C. Monsters (easy version)的AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 2e5 + 10;
 7 
 8 int a[N];
 9 
10 void solve() {
11     int n;
12     scanf("%d", &n);
13     for (int i = 1; i <= n; i++) {
14         scanf("%d", a + i);
15     }
16     sort(a + 1, a + n + 1);
17     LL ret = 0;
18     for (int i = 1; i <= n; i++) {
19         if (a[i] != a[i - 1]) {
20             ret += a[i] - a[i - 1] - 1;
21             a[i] = a[i - 1] + 1;
22         }
23     }
24     printf("%lld\n", ret);
25 }
26 
27 int main() {
28     int t;
29     scanf("%d", &t);
30     while (t--) {
31         solve();
32     }
33     
34     return 0;
35 }
复制代码

  接下来是hard版本的,其实还是有用到easy版的思想的。hard版本就是求数组的每一个前缀使用第一种攻击的最少次数是多少(easy版就是求整个数组使用的第一种攻击最少是多少)。

  在easy版本中a数组可能会存在重复元素,比如有a=[1,1,3,3,3,5],根据上面的算法得到的a=[1,1,2,3,3,4],就会有重复的数字13,那么最小代价就是(11)+(11)+(32)+(33)+(33)+(54)=(1+1+3+3+3+5)(1+1+2+3+3+4)=2。即最小代价为i=1naii=1nai

  而如果a中没有重复的元素出现,那么a必然是1n的形式(即ai=i),那么最小代价就是i=1nain(n+1)2。因此如果知道从a得到a没有重复元素,那么代价就可以通过公式直接求出来而无需遍历a来求a

  之所以要说这个是因为a中从第2个开始的重复元素是不会产生代价的,这意味着对应的ai=ai。这是因为a中相邻两个重复元素的差值为0,而根据构造a贪心原则(只要aiai1>1,那么ai=ai1+1),两个相邻元素的差值最大为1,而只有相邻元素相同即ai=ai1,才会有ai=ai1=ai。因此在a中,把a中重复的元素所对应的数删除,并不会影响最小代价的结果。

  例如a=[1,1,3,3,3,5]a=[1,1,2,3,3,4]a2=1a5=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的数有ci个,对c求前缀和那么就得到si=j=1icj,表示所有数值不超过i的数的个数。因此我们要保证在a的每个前缀中,对于所有的i(这里的i是数值不是下标)都有sii,即保证任何不超过i的数都不多于i,这样才能保证a中不会有重复的数出现,才能用公式i=1maim(m+1)2直接求出前缀的最小代价。

  为此,每次加入一个数x,我们都要对sxsn加上1(题目中值域的最大值为n),然后检查是否存在某个值sy>y(因为每次只增加一个数,因此如果不满足syy,那么必然就是sy=y+1),如果存在则找到满足这个情况的最小的值y,然后删除y,最后代入公式求答案即可。

  解释一下为什么要找到最小的y,因为删除y后,在后面比y大的数中如果存在sx>x的都会变成sxx(因为减少了1),参考下图:

  因此对于a的每个前缀,都需要给一个值域区间加上1,同时还需要判断值域1n是否都满足sii的情况。然后sii其实可以等价成为sii0,因此我们可以用线段树来维护每一个sii。值域区间加上1就相当于给这个区间的sii都加上1,然后判断是否存在sii>0并找到满足这个条件最小的值就相当于判断区间的最大值是否大于0,并通过二分找到最左边满足大于0的那个数。这些都可以用线段树来实现,线段树只需要维护区间的最大值即可。

  AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 2e5 + 10;
 7 
 8 int a[N];
 9 struct Node {
10     int l, r, maxv, add;
11 }tr[N * 4];
12 
13 void build(int u, int l, int r) {
14     if (l == r) {
15         tr[u] = {l, r, -l, 0};
16     }
17     else {
18         int mid = l + r >> 1;
19         build(u << 1, l, mid);
20         build(u << 1 | 1, mid + 1, r);
21         tr[u] = {l, r, max(tr[u << 1].maxv, tr[u << 1 | 1].maxv), 0};
22     }
23 }
24 
25 void pushdown(int u) {
26     if (tr[u].add) {
27         tr[u << 1].add += tr[u].add;
28         tr[u << 1].maxv += tr[u].add;
29         tr[u << 1 | 1].add += tr[u].add;
30         tr[u << 1 | 1].maxv += tr[u].add;
31         tr[u].add = 0;
32     }
33 }
34 
35 void modify(int u, int l, int r, int c) {
36     if (tr[u].l >= l && tr[u].r <= r) {
37         tr[u].maxv += c;
38         tr[u].add += c;
39     }
40     else {
41         pushdown(u);
42         int mid = tr[u].l + tr[u].r >> 1;
43         if (l <= mid) modify(u << 1, l, r, c);
44         if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
45         tr[u].maxv = max(tr[u << 1].maxv, tr[u << 1 | 1].maxv);
46     }
47 }
48 
49 int query(int u) {
50     if (tr[u].l == tr[u].r) return tr[u].l;
51     pushdown(u);
52     if (tr[u << 1].maxv > 0) return query(u << 1);  // 左边存在大于0的值,那么取左边找
53     return query(u << 1 | 1);   // 大于0的值在右边
54 }
55 
56 void solve() {
57     int n;
58     scanf("%d", &n);
59     for (int i = 1; i <= n; i++) {
60         scanf("%d", a + i);
61     }
62     build(1, 1, n);
63     int cnt = 0;    // 当前维护的数的个数
64     LL s = 0;   // 当前维护的数的总和
65     for (int i = 1; i <= n; i++) {
66         cnt++, s += a[i];   // 先记录
67         modify(1, a[i], n, 1);  // a[i]的个数加1,si ~ sn都加1,si-i ~ sn-n都加1
68         if (tr[1].maxv > 0) {   // 存在si-i大于0的数,需要删除最小的那个数
69             int x = query(1);   // 找到最小的那个数
70             modify(1, x, n, -1);    // 删除这个数,区间减1
71             cnt--, s -= x;  // 删除最小的那个数
72         }
73         printf("%lld ", s - cnt * (cnt + 1ll) / 2); // 公式计算答案
74     }
75     printf("\n");
76 }
77 
78 int main() {
79     int t;
80     scanf("%d", &t);
81     while (t--) {
82         solve();
83     }
84     
85     return 0;
86 }
复制代码

 

参考资料

  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

posted @   onlyblues  阅读(175)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示