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_{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;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/14139849.html