📅  最后修改于: 2023-12-03 15:34:33.445000             🧑  作者: Mango
在二进制字符串中给定一个范围 $[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_{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;
}
以上为本题几种常见的解法,可以根据实际情况选用。