实用数据结构小结(树状数组、st表、bitset)


前言

一、树状数组

1.功能介绍

树状数组可以认为是线段树的青春版

树状数组它有着丰富的功能,虽然不及线段树,但它代码简洁,思维清晰,速度也更快

它和st表有点相似,但功能相对更丰富些,树状数组仅靠一维数组便维护了整个区间具体为:
c [ k ] = ∑ i = k − l o w b i t ( k ) + 1 k a [ i ] c[k]=\sum_{i=k-lowbit(k)+1}^{k}a[i] c[k]=i=klowbit(k)+1ka[i] l o w b i t ( x ) = x & ( − x ) lowbit(x)=x\&(-x) lowbit(x)=x&(x)

它支持单点修改、区间修改(需要进行前缀变换)、单点查询、区间查询功能

树状数组的区间查询建立在前缀和的基础上,因此想让它求区间最值还得需要一定的变换,比较复杂,不推荐

int query(int x, int y) {
  int ans = 0;  // (log n) ^ 2
  while (y >= x) {
    ans = max(a[y], ans);
    y--;
    for (; y - lowbit(y) >= x; y -= lowbit(y)) ans = max(c[y], ans);
  }
  return ans;
}

所以它的应用场景一般就集中在数据查询,和逆序对上

此外,值得一提的是它可以与莫队结合求区间逆序对

贴一张图方便理解,图源 https://blog.csdn.net/bestsort/article/details/80796531

2.例题 p3374

单点修改、区间求和

题目链接 洛谷p3374
代码如下:

#include <iostream>
using namespace std;
const int maxn = 5e5 + 5;
int n, m;
int a[maxn], c[maxn];
int lowbit(int x) { return x & (-x); }  // x 最后一位是 1 的位数
void update(int x, int val) {           // c[k] = a[k-lowbit(i)+1] + ... + a[k]
  for (int i = x; i <= n; i += lowbit(i)) c[i] += val;
}
int query(int x) {  // Σ a[i], i <= x
  int sum = 0;
  for (int i = x; i > 0; i -= lowbit(i)) sum += c[i];
  return sum;
}
int main() {
  //   freopen("in.txt", "r", stdin);
  //   freopen("out.txt", "w", stdout);
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]), update(i, a[i]);
  for (int i = 1; i <= m; i++) {
    int x, y, z;
    scanf("%d%d%d", &x, &y, &z);
    if (x == 1) update(y, z);
    if (x == 2) printf("%d\n", query(z) - query(y - 1));
  }
  return 0;
}

2.例题 逆序对p1908

下面给出求逆序对的模板,注意 q u e r y ( x ) = ∑ i = 1 x a [ i ] query(x)=\sum_{i=1}^{x}a[i] query(x)=i=1xa[i]在查询时注意区间边界

题目链接 洛谷p1908

#include <algorithm>
#include <iostream>
#define int long long
using namespace std;
const int maxn = 1e6 + 6;
int n;
int a[maxn], b[maxn], c[maxn];
int lowbit(int x) { return x & (-x); }
void dispersed() {
  for (int i = 1; i <= n; i++) b[i] = a[i];
  sort(b + 1, b + n + 1);
  for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
}
void update(int x, int val) {
  for (int i = x; i <= n; i += lowbit(i)) c[i] += val;
}
int query(int x) {  // Σ a[i], i <= x
  int res = 0;
  for (int i = x; i > 0; i -= lowbit(i)) res += c[i];
  return res;
}
signed main() {
  //   freopen("in.txt", "r", stdin);
  //   freopen("out.txt", "w", stdout);
  scanf("%lld", &n);
  for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
  dispersed();
  int ans = 0;
  for (int i = n; i >= 1; i--) {
    update(a[i], 1);
    ans += query(a[i] - 1);
  }
  printf("%lld\n", ans);
  return 0;
}

二、st表

1.功能介绍

st表在RMQ问题上有着很优秀的复杂度(区间最大、最小查询),代码非常简洁

当然它也有较大的局限性,一般仅用于RMQ这种 “可重复贡献” 的问题,

例如区间GCD、区间按位与也在st表功能范畴内,不过需要注意的是GCD问题上 st 表复杂度不如线段树,

2.例题 p3865


题目链接:洛谷 p3865

#include <iostream>
using namespace std;
const int logn = 21;
const int maxn = 2e6 + 6;
int n, m;
int st[maxn][logn + 1], lg[maxn + 1];
void getST() {
  lg[1] = 0, lg[2] = 1;
  for (int i = 3; i < maxn; i++) lg[i] = lg[i / 2] + 1;

  for (int j = 1; j <= logn; j++)
    for (int i = 1; i + (1 << j) - 1 <= n; i++)  // max{i, i+2^j-1}
      st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
int main() {
  //   freopen("in.txt", "r", stdin);
  //   freopen("out.txt", "w", stdout);
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) scanf("%d", &st[i][0]);
  getST();
  for (int i = 1; i <= m; i++) {
    int x, y;
    scanf("%d%d", &x, &y);
    int s = lg[y - x + 1];
    printf("%d\n", max(st[x][s], st[y - (1 << s) + 1][s]));
  }
  return 0;
}

三、bitset

1.功能介绍

bitset 有许多有用的功能,下面通过代码以及注释的形式给出其主要功能
注意:bs.all()函数是c++11时出现的




2. 调试过程代码

代码如下 :

#include <bitset>
#include <iostream>
using namespace std;
int main() {
  //   freopen("in.txt", "r", stdin);
  //   freopen("out.txt", "w", stdout);

  cout << "num.1-------------------------" << endl;
  bitset<8> bs;
  cout << bs << endl;                     // 默认初始化全为0
  cout << bs.to_ulong() << endl;          // 0
  cout << bs.to_string() << endl;         // 00000000
  cout << bs[0] << " " << bs[1] << endl;  // 从低位开始编号
  cout << bs.count() << endl;             // 统计1的个数
  cout << bs.size() << endl;              // 统计位数
  cout << endl;

  cout << "num.2--------------------------" << endl;
  bitset<8> bs1(7);            // 单目运算
  cout << bs1 << endl;         // 00000111
  cout << (bs1 << 1) << endl;  // 左移一位,多者溢出,少者补0
  cout << (bs1 >> 1) << endl;  // 右移一位,多者溢出,少者补0
  cout << ~bs1 << endl;        // 取反
  cout << endl;

  cout << "num.3--------------------------" << endl;
  bitset<8> bs2("1010");         // 双目运算
  cout << bs2 << endl;           // 00001010
  cout << (bs2 | bs1) << endl;   // 记得带括号,按位或
  cout << (bs2 & bs1) << endl;   // 按位与
  cout << (bs2 ^ bs1) << endl;   // 按位异或
  cout << (bs2 == bs1) << endl;  // 按位比较
  cout << (bs2 != bs1) << endl;  // 按位比较
  cout << endl;

  cout << "num.4--------------------------" << endl;
  bitset<8> bs3("11100000110");  // 前面的溢出,只赋值后面的位
  cout << bs3 << endl;           // 00000110
  cout << bs3.any() << endl;     // bool类型 全0返回0,有1返回1
  cout << bs3.none() << endl;    // bool类型 全0返回1,有1返回0
  cout << bs3.all() << endl;     // bool类型 全1返回1,有0返回0
  cout << bs3.flip(2) << endl;   // 第2位取反
  cout << bs3.flip() << endl;    // 全取反
  cout << endl;

  cout << "num.5--------------------------" << endl;
  char s[10] = "1011";
  bitset<8> bs4(s);
  cout << bs4 << endl;            // 需要 -std=c++11 ,比赛就不能用了
  cout << bs4.set(2) << endl;     // 第2位置1
  cout << bs4.set(3, 1) << endl;  // 第3位置1
  cout << bs4.set() << endl;      // 全部置1
  cout << bs4.reset(2) << endl;   // 第2位清零
  cout << bs4.reset() << endl;    // 全部清零
  return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值