CF679E Bear and Bad Powers of 42
CF679E Bear and Bad Powers of 42
题目来源:Codeforces, Codeforces Round #356 (Div. 1), CF#356, CF679E Bear and Bad Powers of 42
本题题解
根据笔者的习惯,我们称要维护的序列为 \(a_{1\dots n}\),即原题面里的 \(t\)。
设经过任何操作后,序列里的最大数值为 \(\text{maxValue}\)。则 \(\text{maxValue}\leq 10^9 + q\times 10^9\times \log_{42}(\text{maxValue})\)。容易发现 \(\text{maxValue}\) 不会超过 \(\texttt{long long}\) 范围(这只是一个粗略的估计,实际上更小)。
因此,可能出现的 \(42\) 的幂数量非常少,不超过 \(\log_{42}(\text{maxValue})\leq 13\) 个。
先不考虑操作 \(2\)(区间赋值)。
用线段树维护序列,支持区间加。考虑在进行操作 \(3\)(区间加)时,写一个暴力的 \(\texttt{while}\) 循环:只要区间里出现了 \(42\) 的幂,就再加一次。因为每个数最多只会出现 \(\log_{42}(\text{maxValue})\) 次等于 \(42\) 的幂的情况,因此总的操作次数是 \(\mathcal{O}(n\times \log_{42}(\text{maxValue}))\) 的。
于是问题转化为,如何快速判断,区间里是否存在 \(42\) 的幂。
对于序列里的每个数 \(a_i\),记第一个大于等于它的、\(42\) 的幂为 \(b_i\),即 \(b_i = 42^{\lceil\log_{42}(a_i)\rceil}\)。考虑 \(b_i - a_i\) 这个序列,一次“区间加 \(x\)”,相当于令这个序列上每个位置减 \(x\)。当出现某个位置上数值 \(b_i - a_i < 0\) 时,说明该位置的 \(b_i\) 需要更新了,我们暴力更新(根据前面的分析,总更新次数是 \(\mathcal{O}(n\times \log_{42}(\text{maxValue}))\) 的)。更新完后,如果出现某个位置上 \(b_i - a_i = 0\),就说明原序列(\(a\))里存在 \(42\) 的幂,我们继续执行区间加。
也就是说,现在我们维护 \(b_i - a_i\) 这个序列,支持区间加、单点修改(暴力更新 \(b_i\))、求区间最小值。可以用线段树实现,这样单次修改时间复杂度是 \(\mathcal{O}(\log_2(n))\) 的。总时间复杂度 \(\mathcal{O}((n + q)\times \log_{42}(\text{maxValue})\times \log_2(n))\)。
再考虑有操作 \(2\) 的情况。如果我们直接在线段树上执行区间赋值,就可能会影响前面的复杂度分析:因为此时无法保证 \(a\) 序列每个位置上数值都随时间单调变大,也就无法保证总更新次数只有 \(\mathcal{O}(n\times \log_{42}(\text{maxValue}))\) 了。
设一次操作 \(2\) 的区间为 \([l,r]\),考虑只修改 \(r\),然后把区间 \([l,r - 1]\) 设为一个“懒惰状态”。具体来说,懒惰状态满足:
- 在区间加(操作 \(3\))检查更新 \(b_i\) 时,不会被更新到。
- \(a_n\) 不会处于懒惰状态(因为每次只把 \([l,r - 1]\) 设为懒惰状态,显然 \(r -1 < n\))。
- 如果 \(a_i\) 处于懒惰状态,则 \(a_i\) 的实际数值,等于它后面第一个非懒惰状态的值(根据上一条,这样的值一定存在)。
例如,在实现时,我们令 \(b_i\) 不变,把 \(a_i\) 变成 \(-\infty\),这样 \(b_i - a_i\) 就永远 \(> 0\),区间加检查更新时永远不会被更新到。
初始时,所有值都处于非懒惰状态。在执行操作 \(2\) 时,把区间 \([l,r - 1]\) 里所有值暴力设置为懒惰状态(线段树单点修改)。在执行操作 \(2\) 或操作 \(3\) 前,检查 \(l - 1\), \(r\) 这两个位置,如果是懒惰状态,则将它们更新:找出它后面第一个非懒惰状态的值,把被更新的位置赋为这个值。
实现时,可以用一个 \(\texttt{std::set}\) 来维护所有非懒惰状态的位置。更新时,用 \(\texttt{std::set}\) 的 \(\texttt{lower_bound}\) 进行查找。
分析这样做的时间复杂度。在每次操作中,只会增加 \(\mathcal{O}(1)\) 个非懒惰状态(\(l - 1\) 和 \(r\)),因此整个过程中,出现的非懒惰状态总数是 \(\mathcal{O}(n + q)\) 的,这样插入和暴力删除的总时间复杂度都是 \(\mathcal{O}((n + q)\times \log_2(n))\) 的。同时,在线段树中,我们只进行了 \(\mathcal{O}(n + q)\) 次“把某个位置的值重置”的操作,而其它情况下值都是随时间单调变大的,于是可以套用之前的复杂度分析。
综上,我们在 \(\mathcal{O}((n + q)\times \log_{42}(\text{maxValue})\times \log_2(n))\) 的时间复杂度内解决了本题。
参考代码
建议使用快速输入、输出,详见本博客公告。
// problem: CF679E
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 1e5;
const int BASE_NUM = 42;
const ll LL_INF = 1e18;
int n, q, a[MAXN + 5];
set<int> interesting; // "线段树上数值 = 真实值"的位置
class SegmentTree {
private:
int n;
ll arr[MAXN + 5];
ll pow_val[MAXN + 5];
ll mn[MAXN * 4 + 5];
ll lazy[MAXN * 4 + 5];
void add_diff(int p, ll v) {
mn[p] += v;
lazy[p] += v;
}
void update_pow(int i) {
while (pow_val[i] < arr[i]) {
pow_val[i] *= BASE_NUM;
}
}
void push_up(int p) {
mn[p] = min(mn[p << 1], mn[p << 1 | 1]);
}
void push_down(int p) {
if (lazy[p]) {
add_diff(p << 1, lazy[p]);
add_diff(p << 1 | 1, lazy[p]);
lazy[p] = 0;
}
}
void build(int p, int l, int r) {
lazy[p] = 0;
if (l == r) {
pow_val[l] = 1;
update_pow(l);
mn[p] = pow_val[l] - arr[l];
assert(mn[p] != 0);
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
push_up(p);
}
void range_add_diff(int p, int l, int r, int ql, int qr, ll v) {
if (ql <= l && qr >= r) {
add_diff(p, v);
return;
}
push_down(p);
int mid = (l + r) >> 1;
if (ql <= mid) {
range_add_diff(p << 1, l, mid, ql, qr, v);
}
if (qr > mid) {
range_add_diff(p << 1 | 1, mid + 1, r, ql, qr, v);
}
push_up(p);
}
void point_set_real(int p, int l, int r, int pos, ll v) {
if (l == r) {
arr[l] = v;
pow_val[l] = 1;
update_pow(l);
mn[p] = pow_val[l] - arr[l];
return;
}
push_down(p);
int mid = (l + r) >> 1;
if (pos <= mid) {
point_set_real(p << 1, l, mid, pos, v);
} else {
point_set_real(p << 1 | 1, mid + 1, r, pos, v);
}
push_up(p);
}
void kill_neg_diff(int p, int l, int r) {
if (mn[p] >= 0) {
return;
}
if (l == r) {
arr[l] = pow_val[l] - mn[p];
update_pow(l);
mn[p] = pow_val[l] - arr[l];
return;
}
push_down(p);
int mid = (l + r) >> 1;
kill_neg_diff(p << 1, l, mid);
kill_neg_diff(p << 1 | 1, mid + 1, r);
push_up(p);
}
ll point_query_real(int p, int l, int r, int pos) {
if (l == r) {
arr[l] = pow_val[l] - mn[p];
return arr[l];
}
push_down(p);
int mid = (l + r) >> 1;
if (pos <= mid) {
return point_query_real(p << 1, l, mid, pos);
} else {
return point_query_real(p << 1 | 1, mid + 1, r, pos);
}
}
public:
template<typename T>
void build(T* _arr, int _n) {
n = _n;
for (int i = 1; i <= n; ++i)
arr[i] = _arr[i];
build(1, 1, n);
}
// 两个东西: real 是对真实值(arr)操作, diff 是对差值(mn)操作
void range_add_diff(int l, int r, ll v) {
range_add_diff(1, 1, n, l, r, v);
}
void point_set_real(int p, ll v) {
point_set_real(1, 1, n, p, v);
}
void kill_neg_diff() {
kill_neg_diff(1, 1, n);
}
ll global_query_min_diff() {
return mn[1];
}
ll point_query_real(int pos) {
return point_query_real(1, 1, n, pos);
}
SegmentTree() {}
};
SegmentTree T;
ll re_insert(int i, bool need_ret = 0) { // 重新获得真实值
if (interesting.count(i)) {
return (need_ret ? T.point_query_real(i) : -1);
}
set<int> :: iterator it = interesting.upb(i);
assert(it != interesting.end());
ll v = T.point_query_real(*it);
T.point_set_real(i, v);
interesting.insert(i);
return (need_ret ? v : -2);
}
int main() {
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
interesting.insert(i);
}
T.build(a, n);
for (int tq = 1; tq <= q; ++tq) {
int op;
cin >> op;
if (op == 1) {
int pos;
cin >> pos;
cout << re_insert(pos, true) << endl;
} else if (op == 2) {
int l, r;
ll to_set;
cin >> l >> r >> to_set;
re_insert(r);
if (l > 1)
re_insert(l - 1);
T.point_set_real(r, to_set);
while (1) {
set<int> :: iterator it = interesting.lob(l);
assert(it != interesting.end());
if ((*it) == r) {
break;
}
T.point_set_real(*it, -LL_INF);
interesting.erase(it);
}
} else {
int l, r;
ll to_add;
cin >> l >> r >> to_add;
re_insert(r);
if (l > 1)
re_insert(l - 1);
do {
T.range_add_diff(l, r, -to_add); // add_diffference, 给差值 mn 增加 -to_add
T.kill_neg_diff();
} while (T.global_query_min_diff() == 0);
}
// cerr << "**print arr** ";
// for (int i = 1; i <= n; ++i) {
// cerr << T.point_query_real(i) << " \n"[i == n];
// }
}
return 0;
}