• 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;
    }
    
  • 相关阅读:
    4815 江哥的dp题a
    CON1023 明明的计划
    5200 fqy的难题----2的疯狂幂
    [SCOI2005] 最大子矩阵
    1457 又是求和?
    2064 最小平方数
    vijos P1459车展
    1366 xth 的第 12 枚硬币
    1360 xth 的玫瑰花
    3223: Tyvj 1729 文艺平衡树
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14139849.html
Copyright © 2020-2023  润新知