📌  相关文章
📜  Q 查询的给定范围内两个 1 之间的最大 0 计数(1)

📅  最后修改于: 2023-12-03 15:34:33.445000             🧑  作者: Mango

给定范围内两个 1 之间的最大 0 计数

在二进制字符串中给定一个范围 $[l, r]$,查询其中两个 1 之间的最大 0 计数。具体地,假设查询结果为 $ans$,则 $ans$ 表示为 $[l_i, r_i]$ 内两个 1 之间最大 0 的数量。

例如,在字符串 $1010001001$ 中,$[2, 8]$ 内两个 1 之间的最大 0 数量为 3,即 $101 \underline{0001} 001$。

为了快速处理这样的查询,我们需要使用一些高效的数据结构和算法。常用的有线段树、ST 表等。

线段树

我们可以使用线段树或树状数组维护区间内的信息。由于区间内查询的性质,可以通过记录区间内连续 0 的数量、区间内最大连续 0 的数量、区间内两个 1 之间的最大连续 0 的数量等信息来进行处理。

对于每个节点,我们需要记录以下信息:

  • zero_cnt: 当前节点对应的区间内连续 0 的数量。
  • max_zero_cnt: 当前节点对应的区间内最大连续 0 的数量。
  • max_gap_cnt: 当前节点对应的区间内两个 1 之间的最大连续 0 的数量。

为了方便,可以在遍历树的时候维护上面三个值,最后将它们归并到父节点上。归并的时候,有一些细节需要注意,比如两个区间的连接处等。

具体实现可以参考下面的代码。其中 build() 函数用于构建线段树,query() 函数用于进行查询。

const int MAXN = 100005;
int n;
char s[MAXN];

struct Node {
    int zero_cnt, max_zero_cnt, max_gap_cnt;
} tree[MAXN << 2];

void push_up(int p) {
    tree[p].zero_cnt = tree[p << 1].zero_cnt + tree[p << 1 | 1].zero_cnt;
    tree[p].max_zero_cnt = max(tree[p << 1].max_zero_cnt, tree[p << 1 | 1].max_zero_cnt);
    tree[p].max_gap_cnt = max(0, max(tree[p << 1].max_gap_cnt, tree[p << 1 | 1].max_gap_cnt));
    if (tree[p << 1].zero_cnt > 0 && tree[p << 1 | 1].zero_cnt > 0) {
        tree[p].max_gap_cnt = max(tree[p].max_gap_cnt, tree[p << 1].zero_cnt + tree[p << 1 | 1].zero_cnt);
    }
}

void build(int p, int l, int r) {
    if (l == r) {
        if (s[l] == '0') {
            tree[p].zero_cnt = tree[p].max_zero_cnt = 1;
            tree[p].max_gap_cnt = 0;
        } else {
            tree[p].zero_cnt = tree[p].max_zero_cnt = tree[p].max_gap_cnt = 0;
        }
        return;
    }
    int mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    push_up(p);
}

Node query(int p, int l, int r, int ql, int qr) {
    if (ql <= l && qr >= r) {
        return tree[p];
    }
    int mid = (l + r) >> 1;
    bool left_ok = qr <= mid, right_ok = ql > mid;
    Node ret;
    memset(&ret, 0, sizeof(ret));
    if (!left_ok) {
        auto left_res = query(p << 1, l, mid, ql, qr);
        if (right_ok) {
            return left_res;
        }
        ret = left_res;
    }
    if (!right_ok) {
        auto right_res = query(p << 1 | 1, mid + 1, r, ql, qr);
        if (left_ok) {
            return right_res;
        }
        if (ret.zero_cnt > 0 && right_res.zero_cnt > 0) {
            ret.max_gap_cnt = max(ret.max_gap_cnt, ret.zero_cnt + right_res.zero_cnt);
        }
        ret.zero_cnt += right_res.zero_cnt;
        ret.max_zero_cnt = max(ret.max_zero_cnt, right_res.max_zero_cnt);
        ret.max_gap_cnt = max(ret.max_gap_cnt, right_res.max_gap_cnt);
    }
    return ret;
}
ST 表

除了线段树之外,我们还可以利用 ST 表进行查询。ST 表是一个二维的表,其中 $st_{i,j}$ 表示区间 $[i, i+2^j-1]$ 中两个 1 之间的最大 0 的数量。

为了方便计算,我们可以先预处理出一个 $log$ 值表 $lg$,其中 $lg_i$ 表示整数 $i$ 的二进制表示的最高位为 $1$ 的位数。例如,$lg_3=1$,因为 $3=(011)_2$,最高位为第 1 位。

我们可以使用递归的方式来构建 ST 表。对于区间 $[l, r]$,我们可以将其分为两个长度相等的子区间 $[l, mid]$ 和 $[mid+1, r]$,然后递归地处理这些子区间,并计算出两个区间之间的结果。

具体实现可以参考下面的代码。其中 init() 函数用于初始化 ST 表,query() 函数用于进行查询。

const int MAXN = 100005;
int n, lg[MAXN];
char s[MAXN];

int st[MAXN][20];

void init() {
    memset(st, 0, sizeof(st));
    for (int i = 1; i <= n; i++) {
        st[i][0] = s[i] == '0' ? 1 : 0;
    }
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            int k = i + (1 << (j - 1));
            st[i][j] = st[i][j - 1];
            if (k <= n) {
                st[i][j] = max(st[i][j], st[k][j - 1]);
            }
            if (k <= n && s[k] == '1' && s[k - 1] == '0') {
                int len = lg[k - i];
                st[i][j] = max(st[i][j], len);
            }
        }
    }
}

int query(int l, int r) {
    int k = lg[r - l + 1];
    int res = max(st[l][k], st[r - (1 << k) + 1][k]);
    int p = l;
    while (p <= r) {
        if (s[p] == '1') {
            break;
        }
        int len = min(r - p + 1, st[p][k]);
        res = max(res, len);
        p += len;
    }
    int q = r;
    while (q >= p) {
        if (s[q] == '1') {
            break;
        }
        int len = min(q - p + 1, st[q - (1 << k) + 1][k]);
        res = max(res, len);
        q -= len;
    }
    return res;
}

以上为本题几种常见的解法,可以根据实际情况选用。