我们定义一个数组 $a$,其中如果 $S_i$ 等于 $S_{i+1}$,那么 $a_i = 1$,否则 $a_i = 0$。形象一点的说,这个 $a$ 数组就像是“插”在两个字符之间,判断它们是否相等。
这么一来,对于区间 $L,R$,$S_{L..R}$ 是一个合法的字符串,当且仅当 $a_L+a_{L+1}+\cdots+a_{R-1} = 0$ (注意这里是 $a_{R-1}$ 而不是 $a_R$)。
代码中,我们用树状数组 $bit$ 维护 $a$ 数组的前缀和,那么 $2$ 操作就相当于询问 $query(R-1)-query(L-1)$ 是否等于 $0$。
那 $1$ 操作怎么办呢?不妨想一下 $1$ 操作会对 $a$ 数组产生什么影响。对于 $S_{L..R}$,内部的 $01$ 反转并不会对内部的 $a$ 数组的值造成影响——相邻的两个字符,该一样的还是一样,该不一样的还是不一样。对于 $S_{1..L-1}$ 和 $S_{R+1..N}$ 同理。
所以真正改变的 $a$ 数组的值是在交界处,也就是 $a_{L-1}$ (判断 $S_{L-1}$ 和 $S_{L}$ 是否相等)和 $a_R$ (判断 $S_R$ 和 $S_{R+1}$ 是否相等)。更进一步的,其实就是反转了 $a_{L-1}$ 和 $a_{R}$ 的值——原本一样的,其中一个反转后就不一样了,反之亦然。
然后就是简单的实现,注意 $1$ 操作中 $L=1$ 的特判,改 $a_0$ 没有意义,但改了的话这个树状数组的写法会超时。
不懂可以配合代码食用:
#include <bits/stdc++.h>
#define int long long
#define N 500005
using namespace std;
char s[N];
int bit[N], a[N], n, q;
void add(int x, int v) {
while (x < N) bit[x] += v, x += x & -x;
return;
}
int query(int x) {
int res = 0;
while (x) res += bit[x], x -= x & -x;
return res;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> q >> (s+1);
for(int i = 2; i <= n; i++) if (s[i-1] == s[i]) add(i-1, 1), a[i-1] = 1;
for(int i = 1, op, l, r; i <= q; i++) {
cin >> op >> l >> r;
if (op == 1) {
if (a[l-1] == 1) add(l-1, -1), a[l-1] = 0;
else if (l != 1) add(l-1, 1), a[l-1] = 1;
if (a[r] == 1) add(r, -1), a[r] = 0;
else add(r, 1), a[r] = 1;
}
else {
int res = query(r-1)-query(l-1);
if (res == 0) cout << "Yes\n";
else cout << "No\n";
}
}
return 0;
}