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)\) 的贡献。
这样,效率足够通过本题,时间正确。
本文来自博客园,作者:Trent900,转载请注明原文链接:https://www.cnblogs.com/GE9X/p/21580255

浙公网安备 33010602011771号