CF1784C Monsters (hard version) 题解 线段树
题目链接:https://codeforces.com/problemset/problem/1784/C
题目大意:
你面前有 只怪兽,每只怪兽都有一个初始血量,你可以进行两类操作:
- 操作1:选择任意一个血量大于 的怪兽,并将它的血量降低 ;
- 操作2:将所有存活的怪物的血量各自减去 ,如果存在怪物的血量恰好降为 (即这只怪物之前的血量为 ,现在变为 了),则操作2不会结束,而是继续将所有存活的怪兽的血量再各自减去 ,如果仍有怪物的血量恰好降为 ,则继续将所有存活的怪兽的血量再各自减去 ,……,如是操作,直至某次操作后不存在怪物的血量恰好降为 ,或者所有的怪物都被消灭为止。
你有 个子问题需要单独讨论,第 个子问题你需要回答出对于前 值怪兽,将其全部消灭最少需要进行几次操作1。
解题思路:
对于任何一个前缀,最优解肯定是先进行若干次操作1,最后进行一次操作2整体清零。
并且进行操作2的时候,所有的数字升序之后肯定是连续的,假设进行到当前操作的时候有 个不同的数字。
那么对于每一个数字 ,先将 加进去,如果加入 之后有 个数值 的有效数字,则:
- 如果 ,则 肯定可以作为有效数字;
- 如果 ,则 必然无效
而且一旦 无效,对于所有的 ,均要
但是如果加入 之后,存在一个 满足 (因为 增加了 ),则 将由有效变为无效。
此时,有效的数字 需要加入,无效的数字 需要删除(即用 替代 ),即答案加上 ( 和 可能是一样的)。
所以我们可以用线段树维护一个区间信息。
然后我们其实就需要判断 是否有即可。
因为一开始所有 ,所以我们可以维护一个区间 ,一开始第 个点对应的数值为 ,
每当碰到一个数 ,就先将区间 范围内每个数都减小 ,然后判断区间里有没有点对应的数值 ,如果存在某个点 的数值 ,就说明 ,就说明加进来的是一个无效的点,找到最小的 的 ,答案加上 减去 ,因为增加了一个减去 的操作,所以区间 范围内的每个数都要加上 。
否则,加入的是一个合法的点。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int T, n, a[maxn];
int tr[maxn<<2], lazy[maxn<<2];
void push_up(int rt) {
tr[rt] = min(tr[rt<<1], tr[rt<<1|1]);
}
void push_down(int rt) {
if (lazy[rt]) {
tr[rt<<1] += lazy[rt];
lazy[rt<<1] += lazy[rt];
tr[rt<<1|1] += lazy[rt];
lazy[rt<<1|1] += lazy[rt];
lazy[rt] = 0;
}
}
#define lson l, mid, rt<<1
#define rson mid+1, r, rt<<1|1
void build(int l, int r, int rt) {
lazy[rt] = 0;
if (l == r) {
tr[rt] = l;
return;
}
int mid = (l + r) / 2;
build(lson), build(rson), push_up(rt);
}
void add(int L, int R, int x, int l, int r, int rt) {
if (L <= l && r <= R) {
tr[rt] += x;
lazy[rt] += x;
return;
}
push_down(rt);
int mid = (l + r) / 2;
if (L <= mid) add(L, R, x, lson);
if (R > mid) add(L, R, x, rson);
push_up(rt);
}
int query(int l, int r, int rt) {
if (l == r) {
return tr[rt] < 0 ? l : -1;
}
push_down(rt);
int mid = (l + r) / 2;
return (tr[rt<<1] < 0) ? query(lson) : query(rson);
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", a+i);
int m = 0;
long long res = 0;
build(1, n, 1);
for (int i = 1; i <= n; i++) {
add(a[i], n, -1, 1, n, 1);
int x = query(1, n, 1);
if (x == -1)
m++, res += a[i] - m;
else
add(x, n, 1, 1, n, 1), res += a[i] - x;
printf("%lld ", res);
}
puts("");
}
return 0;
}
注:本题还有一种使用 multiset 并且反着推的贪心做法,但是没看懂,可以参考一下 这份代码 ,等以后有机会再补充这个解法 但是 99% 会放鸽子
参考资料:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2022-12-15 洛谷P1486 [NOI2004] 郁闷的出纳员 题解 splay tree