題目大意
給定一個長度為 $n$ 的數列 $a_1, \dots, a_n$ 和 $q$ 筆查詢。每次查詢 $a_l, \dots, a_r$ 中的最大值。
- $1 \leq q \leq n \leq 5 \cdot 10^5$
- $1 \leq a_i < 2^{31}$
題解
本題是經典的 RMQ 問題。RMQ 有許多做法,使用資料結構的話可以使用稀疏表 (sparse table) 或是線段樹去實作,甚至也能夠使用分塊。以下提供這三種資料結構的 code 和解說。1. 稀疏表 (sparse table)
只能處理靜態 (不帶更新) 的操作。
#include <bits/stdc++.h>
template<class S, S (*op)(S, S)>
struct sparse_table {
public:
sparse_table() {}
explicit sparse_table(const std::vector<S>& a) {
n = (int) a.size();
int max_log = std::__lg(n) + 1;
mat.resize(max_log);
mat[0] = a;
for(int j = 1; j < max_log; ++j) {
mat[j].resize(n - (1 << j) + 1);
for(int i = 0; i <= n - (1 << j); ++i) {
mat[j][i] = op(mat[j - 1][i], mat[j - 1][i + (1 << (j - 1))]);
}
}
}
S prod(int from, int to) const {
assert(0 <= from && from <= to && to <= n - 1);
int lg = std::__lg(to - from + 1);
return op(mat[lg][from], mat[lg][to - (1 << lg) + 1]);
}
private:
int n;
std::vector<std::vector<S>> mat;
};
using namespace std;
int op_max(int a, int b) { return max(a, b); }
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i++) {
cin >> a[i];
}
sparse_table<int, op_max> st(a);
int q;
cin >> q;
while(q--) {
int l, r;
cin >> l >> r;
l--, r--;
if(l > r) {
swap(l, r);
}
cout << st.prod(l, r) << "\n";
}
return 0;
}
2. 線段樹
以下的程式碼為 zkw 線段樹,是一種使用迴圈實作的版本。因為沒有使用到遞迴,因此常數很小。
#include <bits/stdc++.h>
template<class S, S (*e)(), S (*op)(S, S)>
struct segtree {
public:
segtree() {}
explicit segtree(int _n) : segtree(std::vector<S>(_n, e())) {}
explicit segtree(const std::vector<S>& a): n(a.size()) {
log = std::__lg(2 * n - 1);
size = 1 << log;
d.resize(size * 2, e());
for(int i = 0; i < n; ++i) {
d[size + i] = a[i];
}
for(int i = size - 1; i >= 1; i--) {
update(i);
}
}
void set(int p, S val) {
assert(0 <= p && p < n);
p += size;
d[p] = val;
for(int i = 1; i <= log; ++i) {
update(p >> i);
}
}
S get(int p) const {
assert(0 <= p && p < n);
return d[p + size];
}
S operator[](int p) const { return get(p); }
S prod(int l, int r) const {
assert(0 <= l && l <= r && r <= n);
S sml = e(), smr = e();
for(l += size, r += size; l < r; l >>= 1, r >>= 1) {
if(l & 1) {
sml = op(sml, d[l++]);
}
if(r & 1) {
smr = op(d[--r], smr);
}
}
return op(sml, smr);
}
S all_prod() const { return d[1]; }
template<bool (*f)(S)> int max_right(int l) {
return max_right(l, [](S x) { return f(x); });
}
template<class F> int max_right(int l, F f) {
assert(0 <= l && l <= n);
assert(f(e()));
if(l == n) {
return n;
}
l += size;
S sm = e();
do {
while(~l & 1) {
l >>= 1;
}
if(!f(op(sm, d[l]))) {
while(l < size) {
push(l);
l <<= 1;
if(f(op(sm, d[l]))) {
sm = op(sm, d[l++]);
}
}
return l - size;
}
sm = op(sm, d[l++]);
} while((l & -l) != l);
return n;
}
template<bool (*f)(S)> int min_left(int r) {
return min_left(r, [](S x) { return f(x); });
}
template<class F> int min_left(int r, F f) {
assert(0 <= r && r <= n);
assert(f(e()));
if(r == 0) {
return 0;
}
r += size;
S sm = e();
do {
r--;
while(r > 1 && (r & 1)) {
r >>= 1;
}
if(!f(op(d[r], sm))) {
while(r < size) {
push(r);
r = 2 * r + 1;
if(f(op(d[r], sm))) {
sm = op(d[r--], sm);
}
}
return r + 1 - size;
}
sm = op(d[r], sm);
} while((r & -r) != r);
return 0;
}
protected:
int n, size, log;
std::vector<S> d;
void update(int v) {
d[v] = op(d[2 * v], d[2 * v + 1]);
}
virtual void push(int p) {}
};
using namespace std;
int e() { return 0; }
int op_max(int a, int b) { return max(a, b); }
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i++) {
cin >> a[i];
}
segtree<int, e, op_max> seg(a);
int q;
cin >> q;
while(q--) {
int l, r;
cin >> l >> r;
l--, r--;
if(l > r) {
swap(l, r);
}
cout << seg.prod(l, r + 1) << "\n";
}
return 0;
}
3. 分塊
使用分塊的時候塊的大小可以視情況調整。以下的 code 塊的大小取 $650$ 時跑 $0.9$s,取 $700$ 和 $800$ 跑 $0.4$s。
#include <bits/stdc++.h>
using namespace std;
const int BLOCK_SIZE = 700;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i++) {
cin >> a[i];
}
vector<int> block((n + BLOCK_SIZE - 1) / BLOCK_SIZE);
auto get_block_id = [](int i) {
return i / BLOCK_SIZE;
};
for(int i = 0; i < n; i++) {
block[get_block_id(i)] = max(block[get_block_id(i)], a[i]);
}
int q;
cin >> q;
while(q--) {
int l, r;
cin >> l >> r;
l--, r--;
if(l > r) {
swap(l, r);
}
int lid = get_block_id(l), rid = get_block_id(r);
int ans = 0;
if(lid == rid) {
for(int i = l; i <= r; i++) {
ans = max(ans, a[i]);
}
} else {
for(int i = l; i < n && lid == get_block_id(i); i++) {
ans = max(ans, a[i]);
}
for(int i = r; i >= 0 && rid == get_block_id(i); i--) {
ans = max(ans, a[i]);
}
for(int i = lid + 1; i < rid; i++) {
ans = max(ans, block[i]);
}
}
cout << ans << "\n";
}
return 0;
}
如果本文對您有幫助的話幫忙點擊廣告和分享吧!
© 若無特別註明,本站文章皆由 WeakMouse's Coding Blog 原創 ,轉載引用本文前請先留言告知。本文轉載請註明文章源自 WeakMouse's Coding Blog ,作者 ,並附上原文連結: 【題解】Zerojudge d539 - 區間 MAX
0 留言