ST表(Sparse Table) -Updating

ST 表是用于解决 可重复贡献问题 的数据结构,是一种离线算法。

可重复贡献问题:简单来说就是求max或min的顺序是不影响结果,每个值可以被多次包括、计算。

常见的可重复贡献问题有:区间最值、区间按位和、区间按位或、区间GCD等。

时间复杂度和空间复杂度

  • 时间复杂度:
    处理ST表:O(log2 n)
    查询ST表:O(1)
  • 空间复杂度:O(n log n)

优缺点

  • 优点: 时间复杂度低,代码量相对线段树少了不少。
  • 缺点: 维护信息有限,拓展较少,修改困难。

核心代码

处理ST表(储存区间最大值)
void initSparseTable(vector<int> a) {
  int la = a.size(), l2 = ceil(log2(la)) + 1;
  for (int i = 0; i < la; ++i) ST[i][0] = a[i];
  for (int j = 1; j < l2; ++j) {
    int pj = 1 << (j - 1);
    for (int i = 0; i + pj < la; ++i) {
      ST[i][j] = max(ST[i][j - 1], ST[i + pj][j - 1]);
    }
  }
}
查询ST表(储存区间最大值)
int Query(int l, int r) {
  int lt = r - l + 1;
  int t = lt == 1 ? 0 : ceil(log2(lt)) - 1;
  return max(ST[l][t], ST[r - (1 << t) + 1][t]);
}
反向ST表实现边建边查(储存最大值)
void UpdateSparseTable() {
  ST[n][0] = a[n];
  for (int j = 1; (1 << j) <= n; ++j) {
    ST[n][j] = max(ST[n][j - 1], ST[n - (1 << (j - 1))][j - 1]);
  }
}
int Query(int l, int r) {
  int lt = r - l + 1;
  int t = lt == 1 ? 0 : ceil(log2(lt)) - 1;
  return max(ST[r][t], ST[l + (1 << t) - 1][t]);
}

练习题目

1.质量检测

题意描述

求区间最小值。

解题思路

ST表

AC代码
/**
 * \link: https://www.luogu.com.cn/problem/P2251
 * \category: ST表 SparseTable 单调队列
 *
 * 
 * 
 **/
// #pragma GCC optimize("O2")

#include <algorithm>
#include <iostream>
#include <cmath>
#include <deque>

#define DBG(x) cout << #x << " = " << (x) << '\n'

using namespace std;

/** using SparseTable. */
const int kN = 1e6 + 233;
vector<vector<int> > ST(kN, vector<int>(21));
void initSparseTable(vector<int> &data) {
  int ld = data.size(), l2 = ceil(log2(ld)) + 1;
  for (int i = 0; i < ld; ++i) ST[i][0] = data[i];
  for (int j = 1; j < l2; ++j) {
    int pj = 1 << (j - 1);
    for (int i = 0; i + pj< ld; ++i) {
      ST[i][j] = min(ST[i][j - 1], ST[i + pj][j - 1]);
    }
  } 
}

int Query(int l, int r) {
  int lt = r - l + 1;
  int t = lt == 1 ? 0 : ceil(log2(lt)) - 1;
  return min(ST[l][t], ST[r - (1 << t) + 1][t]);
}

inline void solve() {
  int n, k; cin >> n >> k;
  vector<int> data(n);
  for (auto &it : data) cin >> it;
  initSparseTable(data);
  for (int i = k - 1; i < n; ++i) {
    cout << Query(i - k + 1, i) << '\n';
  }
  return;
}

/** using monotonic queue. */
inline void solve2() {
  int n, k; cin >> n >> k;
  vector<int> data(n);
  for (auto &it : data) cin >> it;
  vector<int> ans;
  deque<int> que;
  for (int i = 0; i < n; ++i) {
    while (que.size() && data.at(i) <= data.at(que.back())) que.pop_back(); 
    while (que.size() && que.front() <= i - k) que.pop_front();
    if (i >= k - 1) {
      if (que.size()) ans.push_back(data.at(que.front()));
      else ans.push_back(data.at(i));
    }
    que.push_back(i);
  }
  for (auto item : ans) cout << item << '\n';
}

bool rt = false;
signed main() {
  ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
  freopen("/Users/Shared/test.in", "r", stdin);
#endif
  if (rt) { int T; cin >> T; while (T--) solve(); }
  else solve();
  return (0 ^ 0);
}

2. Balanced Lineup G

题意描述

求区间最大值减最小值

解题思路

双倍ST表

AC代码
/**
 * \problem: P2880 [USACO07JAN] Balanced Lineup G
 * \link: https://www.luogu.com.cn/problem/P2880
 * \category: SparseTable 树状树组
 * \date: Fri Jul 14 09:43:28 CST 2023 - Fri Jul 14 10:29:30 CST 2023
 * 
 **/
#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

const int kN = 2e5 + 233;
vector<vector<int> > ST1(kN, vector<int>(31));
vector<vector<int> > ST2(kN, vector<int>(31));

void initSparseTable(vector<int> data) {
  int ld = data.size(), l2 = (int)ceil(log2(ld)) + 1;
  for (int i = 0; i < ld; ++i) {
    ST1[i][0] = data.at(i);
    ST2[i][0] = data.at(i);
  }
  for (int j = 1; j < l2; ++j) {
    int pj = 1 << (j - 1);
    for (int i = 0; i + pj < ld; ++i) {
      ST1[i][j] = max(ST1[i][j - 1], ST1[i + pj][j - 1]);
      ST2[i][j] = min(ST2[i][j - 1], ST2[i + pj][j - 1]);
    }
  }
}

int Query(int l, int r, bool isMax) {
  int lt = r - l + 1;
  int t = lt == 1 ? 0 : (int) ceil(log2(lt)) - 1;
  if (isMax)  return max(ST1[l][t], ST1[r - (1 << t) + 1][t]);
  else        return min(ST2[l][t], ST2[r - (1 << t) + 1][t]);
}

inline void solve() {
  int n, q; cin >> n >> q;
  vector<int> data(n);
  for (auto &it : data) cin >> it;
  initSparseTable(data);
  while (q--) {
    int l, r; cin >> l >> r;
    int maxx = Query(l - 1, r - 1, true);
    int minn = Query(l - 1, r - 1, false);
//    cout << "test:" << maxx <<  ' ' << minn << '\n';
    cout << maxx - minn << '\n';
  }
}

bool rt = false;

signed main() {
  ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
#ifndef ONLINE_JUDGE
  freopen("test.in", "r", stdin);
#endif
  if (rt) { int T; cin >> T; while (T--) solve(); }
  else solve();
  return (0 ^ 0);
}

3.最大数

题目描述

向末尾添加元素的同时求区间最大值

解题思路

反向ST表实现

γυ

AC代码
/**
 * \link: https://www.luogu.com.cn/problem/P1198
 * \category: SparseTable
 *
 * \date:
 **/
// #pragma GCC optimize("O2")

#include <algorithm>
#include <iostream>
#include <cmath>
#define DBG(x) cout << #x << " = " << (x) << '\n'
#define int long long

using namespace std;

const int kN = 2e5 + 233;
int n = 0;
vector<int> a(kN);
vector<vector<int> > ST(kN, vector<int>(31));

void UpdateSparseTable() {
  ST[n][0] = a[n];
  for (int j = 1; (1 << j) <= n; ++j) {
    ST[n][j] = max(ST[n][j - 1], ST[n - (1 << (j - 1))][j - 1]);
  }
}

int Query(int l, int r) {
  int lt = r - l + 1;
  int t = lt == 1 ? 0 : ceil(log2(lt)) - 1;
  return max(ST[r][t], ST[l + (1 << t) - 1][t]);
}

inline void solve() {
  int q, MOD; cin >> q >> MOD;
  int t = 0;
  while (q--) {
    char ch; cin >> ch;
    switch(ch) {
      case 'A': {
        int x; cin >> x;
        a[++n] = (x + t) % MOD;
        UpdateSparseTable();
        break;
      }
      case 'Q' : {
        int x; cin >> x;
        cout << (t = Query(n - x + 1, n)) << '\n';
        break;
      }
    }
  }
}

bool rt = false;
signed main() {
  ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
  freopen("test.in", "r", stdin);
#endif
  if (rt) { int T; cin >> T; while (T--) solve(); }
  else solve();
  return (0 ^ 0);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值