CF679E Bear and Bad Powers of 42
题目来源:Codeforces, Codeforces Round #356 (Div. 1), CF#356, CF679E Bear and Bad Powers of 42
本题题解
根据笔者的习惯,我们称要维护的序列为 (a_{1dots n}),即原题面里的 (t)。
设经过任何操作后,序列里的最大数值为 ( ext{maxValue})。则 ( ext{maxValue}leq 10^9 + q imes 10^9 imes log_{42}( ext{maxValue}))。容易发现 ( ext{maxValue}) 不会超过 ( exttt{long long}) 范围(这只是一个粗略的估计,实际上更小)。
因此,可能出现的 (42) 的幂数量非常少,不超过 (log_{42}( ext{maxValue})leq 13) 个。
先不考虑操作 (2)(区间赋值)。
用线段树维护序列,支持区间加。考虑在进行操作 (3)(区间加)时,写一个暴力的 ( exttt{while}) 循环:只要区间里出现了 (42) 的幂,就再加一次。因为每个数最多只会出现 (log_{42}( ext{maxValue})) 次等于 (42) 的幂的情况,因此总的操作次数是 (mathcal{O}(n imes log_{42}( ext{maxValue}))) 的。
于是问题转化为,如何快速判断,区间里是否存在 (42) 的幂。
对于序列里的每个数 (a_i),记第一个大于等于它的、(42) 的幂为 (b_i),即 (b_i = 42^{lceillog_{42}(a_i) ceil})。考虑 (b_i - a_i) 这个序列,一次“区间加 (x)”,相当于令这个序列上每个位置减 (x)。当出现某个位置上数值 (b_i - a_i < 0) 时,说明该位置的 (b_i) 需要更新了,我们暴力更新(根据前面的分析,总更新次数是 (mathcal{O}(n imes log_{42}( ext{maxValue}))) 的)。更新完后,如果出现某个位置上 (b_i - a_i = 0),就说明原序列((a))里存在 (42) 的幂,我们继续执行区间加。
也就是说,现在我们维护 (b_i - a_i) 这个序列,支持区间加、单点修改(暴力更新 (b_i))、求区间最小值。可以用线段树实现,这样单次修改时间复杂度是 (mathcal{O}(log_2(n))) 的。总时间复杂度 (mathcal{O}((n + q) imes log_{42}( ext{maxValue}) imes log_2(n)))。
再考虑有操作 (2) 的情况。如果我们直接在线段树上执行区间赋值,就可能会影响前面的复杂度分析:因为此时无法保证 (a) 序列每个位置上数值都随时间单调变大,也就无法保证总更新次数只有 (mathcal{O}(n imes log_{42}( ext{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) 这两个位置,如果是懒惰状态,则将它们更新:找出它后面第一个非懒惰状态的值,把被更新的位置赋为这个值。
实现时,可以用一个 ( exttt{std::set}) 来维护所有非懒惰状态的位置。更新时,用 ( exttt{std::set}) 的 ( exttt{lower_bound}) 进行查找。
分析这样做的时间复杂度。在每次操作中,只会增加 (mathcal{O}(1)) 个非懒惰状态((l - 1) 和 (r)),因此整个过程中,出现的非懒惰状态总数是 (mathcal{O}(n + q)) 的,这样插入和暴力删除的总时间复杂度都是 (mathcal{O}((n + q) imes log_2(n))) 的。同时,在线段树中,我们只进行了 (mathcal{O}(n + q)) 次“把某个位置的值重置”的操作,而其它情况下值都是随时间单调变大的,于是可以套用之前的复杂度分析。
综上,我们在 (mathcal{O}((n + q) imes log_{42}( ext{maxValue}) imes 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) << "
"[i == n];
// }
}
return 0;
}