思路:
对于每个线段树区间维护一个矩阵,(i,j)表示从后面进位j,向前进位i 的最小更新次数
矩阵的转移方程为 dp[i][j] = min(dp[i][0]+dp[0][j], dp[i][1]+dp[1][j])
此时线段树的更新就显而易见了。
之前的想法是连续1的串长大于1的串可以用2次操作消除,但是形如1110111中间只有一个0的少用1次操作消除,维护左右端点1的个数以及是否为单个0。
#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 3e5 + 5;
struct nd {
int m[2][2];
nd(int x = 0) {
m[0][1] = m[1][0] = 1;
m[0][0] = x;
m[1][1] = !x;
}
}dat[MAX_N * 4];
int cnt = 0;
char s[MAX_N];
int n, q, a, b;
nd merge(nd a, nd b) {
nd c;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c.m[i][j] = min(a.m[i][0] + b.m[0][j], a.m[i][1] + b.m[1][j]);
}
}
return c;
}
void build(int l, int r, int k) {
if (r - l == 1) {
if (s[cnt++] == '1')
dat[k] = nd(1);
else
dat[k] = nd(0);
return;
}
build(l, (l + r) >> 1, 2 * k + 1);
build((l + r) >> 1, r, 2 * k + 2);
dat[k] = merge(dat[2 * k + 1], dat[2 * k + 2]);
}
nd query(int a, int b, int l, int r, int k) {
if (a <= l && r <= b) return dat[k];
if (b <= l || a >= r) return nd(0);
return merge(query(a, b, l, (l + r) >> 1, 2 * k + 1),
query(a, b, (l + r) >> 1, r, 2 * k + 2));
}
void change(int l, int r, int k) {
if (a < l || a >= r) return;
if (r - l == 1) {
if (b == 1)
dat[k] = nd(1);
else
dat[k] = nd(0);
return;
}
change(l, (l + r) >> 1, 2 * k + 1);
change((l + r) >> 1, r, 2 * k + 2);
dat[k] = merge(dat[2 * k + 1], dat[2 * k + 2]);
}
int main() {
cin >> n; scanf("%s", &s);
build(0, n, 0);
cin >> q;
int t;
for (int i = 0; i < q; i++) {
scanf("%d%d%d", &t, &a, &b);
if (t == 1) {
printf("%d\n",query(a - 1, b, 0, n, 0).m[0][0]);
}
else {
a--;
change(0, n, 0);
}
}
}