HDU-5828 Rikka with Sequence

题意:你需要维护一个数组。支持区间加,区间开根,区间求和。

我们做过没有区间加的版本。这个版本中,我们发现,对于一个数,暴力给它开一次根会给时间复杂度带来 \(\mathcal{O}(\log n)\) 的贡献。那么数不会增加,所以在吧遍历树的时候,将最大值 $ \le 1 $ 的区间剪枝即可。时间复杂度 \(\mathcal{O}(k n\log n)\)\(k\) 是一个数最多可以开方的次数。

#include <bits/stdc++.h>
#define ll long long
#define ls (x * 2)
#define rs (x * 2 + 1)
#define mid ((l + r) / 2)
#define ofr (r < ql || qr < l)
#define ir (ql <= l && r <= qr)
using namespace std;
const int N = 1e5 + 7;

struct segt{
	ll a[N << 2], t[N << 2];
	void init(){
		memset(a, 0, sizeof 0);
		memset(t, 0, sizeof 0);
	}
	void upd(int x){
		a[x] = sqrt(a[x]);
		if(a[x] <= 1) t[x] = 1;
	}
	void push_up(int x){
		a[x] = a[ls] + a[rs];
		t[x] = t[ls] & t[rs];
	}
	void insert(int x, int l, int r, int pos, ll k){
		if(r < pos || pos < l) return;
		if(l == pos && r == pos){
			a[x] = k;
			if(a[x] <= 1) t[x] = 1;
			return;
		}
		insert(ls, l, mid, pos, k);
		insert(rs, mid + 1, r, pos, k);
		push_up(x);
	}
	void op(int x, int l, int r, int ql, int qr){
		if(ofr) return;
		if(t[x]) return;
		if(l == r){
			upd(x);
			return;
		}
		op(ls, l, mid, ql, qr);
		op(rs, mid + 1, r, ql, qr);
		push_up(x);
	}
	ll qur(int x, int l, int r, int ql, int qr){
		if(ofr) return 0;
		if(ir) return a[x];
		ll res = 0;
		res += qur(ls, l, mid, ql, qr);
		res += qur(rs, mid + 1, r, ql, qr);
		return res;
	}
}tr;

int n, m;

int main(){
	tr.init();
	cin >> n;
	for(int i = 1; i <= n; i ++){
		ll k;
		cin >> k;
		tr.insert(1, 1, n, i, k);
	}
	cin >> m;
	while(m --){
		int opt, l, r;
		cin >> opt >> l >> r;
		if(l > r) swap(l, r);
		if(opt == 0){
			tr.op(1, 1, n, l, r);
		}
		else{
			cout << tr.qur(1, 1, n, l, r) << "\n";
		}
	}
	return 0;
}

加上了区间加这个操作后,一个数开方的次数就没有限制了。我们可以全部开方,全部加一个大数交替,把上述解法卡到 \(\mathcal{O}(nq)\),TLE 代码:

// Problem: B - Rikka with Sequence
// Contest: Virtual Judge - ZR 2025 Summer C 7.18
// Memory Limit: 1024 MB
// Time Limit: 1000 ms

#include <iostream>
#include <cmath>
#include <algorithm>
#define int long long
#define Misaka namespace
#define Network std
using Misaka Network;
const int N = 1e5 + 7;
int SQ(int x){
	int s = sqrt(x);
	if((s + 1) * (s + 1) <= x) return s + 1;
	return s;
}

struct SGT{
	int val[N << 2], mx[N << 2], lz[N << 2];
	#define ls (x << 1)
	#define rs ((x << 1) | 1)
	#define mid ((l + r) >> 1)
	void build(int x, int l, int r){
		if(l == r) return cin >> val[x], mx[x] = val[x], void();
		build(ls, l, mid), build(rs, mid + 1, r);
		val[x] = val[ls] + val[rs], mx[x] = max(mx[ls], mx[rs]);
	}
	void pushdown(int x, int l, int r){
		lz[ls] += lz[x], lz[rs] += lz[x];
		val[ls] += (mid - l + 1) * lz[x];
		val[rs] += (r - mid) * lz[x];
		mx[ls] += lz[x], mx[rs] += lz[x];
		lz[x] = 0;
	}
	void Mdf(int x, int l, int r, int ql, int qr){
		if(mx[x] <= 1) return;
		if(l == r) return mx[x] = val[x] = SQ(mx[x]), void();
		pushdown(x, l, r);
		if(ql <= mid && mx[ls] > 1) Mdf(ls, l, mid, ql, qr);
		if(mid < qr  && mx[rs] > 1) Mdf(rs, mid + 1, r, ql, qr);
		val[x] = val[ls] + val[rs], mx[x] = max(mx[ls], mx[rs]);
	}
	void add(int x, int l, int r, int ql, int qr, int k){
		if(ql <= l && r <= qr){
			val[x] += (r - l + 1) * k, lz[x] += k;
			mx[x] += k;
			return; 
		}
		pushdown(x, l, r);
		if(ql <= mid) add(ls, l, mid, ql, qr, k);
		if(mid < qr)  add(rs, mid + 1, r, ql, qr, k);
		val[x] = val[ls] + val[rs], mx[x] = max(mx[ls], mx[rs]);
	}
	int sum(int x, int l, int r, int ql, int qr){
		if(ql <= l && r <= qr) return val[x];
		pushdown(x, l, r);
		int res = 0;
		if(ql <= mid) res += sum(ls, l, mid, ql, qr);
		if(mid < qr)  res += sum(rs, mid + 1, r, ql, qr);
		return res;
	}
} sgt;
int n, m;

void solve(){
	
	cin >> n >> m;
	sgt.build(1, 1, n);
	
	while(m --){
		int opt; cin >> opt;
		if(opt == 1){
			int l, r, x; cin >> l >> r >> x;
			sgt.add(1, 1, n, l, r, x);
		}
		else if(opt == 2){
			int l, r; cin >> l >> r;
			sgt.Mdf(1, 1, n, l, r);
		}
		else{
			int l, r; cin >> l >> r;
			cout << sgt.sum(1, 1, n, l, r) << "\n";
		}
	}
	
}

signed main(){
	ios::sync_with_stdio(0), cin.tie(0);
	int t; cin >> t;
	while(t --) solve();
	return 0;
}

那么接下来,我们需要重新找一个势能维护。如果你的观察力足够惊人,你就会发现,再进行这三个操作的过程中,数组的极差呈下降趋势。由此,我们发明如下改进:

继续暴力开方的前提下,在线段树上维护每一个节点区间内的最大值和最小值。每次开根到这个区间时,如果 \(mx = mn\),就说明这个区间所有数相等。此时操作等效于区间减,我们直接把标记打在 lazy tag 上,不需要再往下了。

我们来具体分析一下复杂度:

首先,区间加,区间求和,均为 \(\log n\) 的。

接着大概分析区间开方:对于操作区间的一个节点,如果它的极差为 \(0\),那么它不会贡献复杂度。如果它的极差大于 \(0\),那么它会贡献 \(1\) 的复杂度。紧接着我们发现,一个节点每贡献 \(1\) 的复杂度(这个区间里的数被全部开根),极差会至少减少一半(这个估计太保守了)。因此,一个节点会贡献 \(\log 极差\) 次复杂度。我们都知道线段树有 \(\mathcal{O}(n)\) 个节点。

接下来计算区间加给若干节点极差带来的贡献。已知一次区间加 \(x\),会给那些没有被完全覆盖的节点的极差带来 \(x\) 的增加。这些节点一共有多少个呢?显然是线段树的层数个。那吗,一次区间加 \(x\),会给复杂度带来 \(\log n \cdot \log x\) 的贡献。而且这个两只 \(\log\) 还跑不满。那么 \(q\) 次操作,最多给复杂度带来 \(\mathcal{O}(q \cdot \log ^ 2 n)\) 的贡献。

这样,效率足够通过本题,时间正确。

posted @ 2026-07-17 15:20  Trent900  阅读(5)  评论(0)    收藏  举报