P11236 [KTSC 2024 R1] 水果游戏
EuphoricStar · · 题解
这题如果不带修有一个
考虑将相同的数缩成若干个连续段
如果出现了一个谷
设
否则两边不可能并起来,把
这样整个序列就变成了一个单峰序列,连续段个数是
发现这个信息是可以合并的,所以可以上线段树支持单点修改。合并两个信息就把右边信息的所有连续段逐个加入到左边信息中。一个连续段添加到一个信息中,就看有没有新产生谷,如果产生了就递归加入新的连续段。
时间复杂度
以下的代码在 QOJ 同时获得了最短解和最优解。
#include <bits/stdc++.h>
using namespace std;
int n, N;
struct node {
int t, k, a[22][2];
node(int x = 1e9) {
t = (x < 1e8 ? x : 0);
a[k = 1][0] = x;
a[k][1] = 1;
}
void add(int x, int y) {
if (!y) {
return;
}
while (k >= 2 && a[k - 1][0] > a[k][0] && a[k][0] < x) {
int p = a[k][0], q = a[k][1];
if (a[--k][0] > 1e8 && x > 1e8) {
continue;
}
int d = min(a[k][0], x) - p;
add(p + d, q >> d);
if (q & ((1 << d) - 1)) {
add(1e9, 1);
add(p + d, q >> d);
}
}
if (k && a[k][0] == x) {
a[k][1] += y;
} else {
a[++k][0] = x;
a[k][1] = y;
}
if (a[k][0] < 1e8) {
t = max(t, a[k][0] + __lg(a[k][1]));
}
}
} a[262199];
inline node operator + (node a, node b) {
a.t = max(a.t, b.t);
for (int i = 1; i <= b.k; ++i) {
a.add(b.a[i][0], b.a[i][1]);
}
return a;
}
void prepare_game(vector<int> b) {
n = (int)b.size();
N = 1 << (__lg(n + 1) + 1);
for (int i = 1; i <= n; ++i) {
a[i + N] = node(b[i - 1]);
}
for (int i = N; i; --i) {
a[i] = a[i << 1] + a[i << 1 | 1];
}
}
int play_game(int l, int r) {
node u(1e9), v(1e9);
for (l += N, r += N + 2; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (!(l & 1)) {
u = u + a[l ^ 1];
}
if (r & 1) {
v = a[r ^ 1] + v;
}
}
return (u + v).t;
}
void update_game(int x, int y) {
a[x += N + 1] = node(y);
while (x >>= 1) {
a[x] = a[x << 1] + a[x << 1 | 1];
}
}