题意:
给出一个01串,有两种操作,操作一是将某一个位置的数字修改,操作二是询问某一个区间,将这个区间看做1个二进制数,可以随意加减2的幂次,问将这个数变为0的最小操作步数。
题解:
对于一个区间,用变成0用4种情况
- 从后面进一位,不向前面进位
- 从后面进一位,向前面进位
- 不从后面进一位,不向前面进位
- 不从后面进一位,向前面进位
比如,110011
情况1:由于从后面进位,实际需要改变的是 110100,最终改变为 000000
情况2:由于从后面进位,实际需要改变的是 110100,要向前进位,所以最终改变为 1000000 (后六位满足全是0)
情况3:实际需要改变的是 110011,最终改变为 000000
情况4:实际需要改变的是 110011,最终改变为 1000000
用 dp[ i ] [ j ] 表示从后面进 j 位,向前进 i 位 ,变成0的最小步数
区间A由区间B、C合并得来,即 A=B+C
dp[ i ] [ j ] (A) =min{ dp [ i ] [ 0 ] (B)+dp[ 0 ] [ j ](C), dp [ i ] [ 1 ] (B)+dp[ 1 ] [ j ](C) }
最后答案就是整个区间 [ 1 , n ] 的 dp [ 0 ] [ 0 ] 的值,这是显然的,不能从后进位,不能向前进位,因为第0位不能有1
用线段树维护即可
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=3e5 + 5;
struct node {
int m[2][2];
node(int x=0) {
m[0][1]=m[1][0]=1;
m[0][0]=x;
m[1][1]=!x;
}
friend node operator + (node a,node b){
node 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;
}
}d[N * 4];
char s[N];
int n,q,a,b;
void build(int l,int r,int k) {
if (r==l)
{
d[k]=node(s[l]=='1');
return;
}
int mid=l+r>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
d[k]=d[k<<1]+d[k<<1|1];
}
node query(int a,int b,int l,int r,int k) {
if (a==l && r==b) return d[k];
int mid=l+r>>1;
if (b<=mid) return query(a,b,l,mid,k<<1);else
if (a>mid) return query(a,b,mid+1,r,k<<1|1);else
return query(a,mid,l,mid,k<<1)+query(mid+1,b,mid+1,r,k<<1|1);
}
void change(int l,int r,int k) {
if (r == l) {
d[k]=node(b==1);
return;
}
int mid=l+r>>1;
if (a<=mid) change(l,mid,k<<1);else
change(mid+1,r,k<<1|1);
d[k]=d[k<<1]+d[k<<1|1];
}
int main() {
cin>>n;
scanf("%s",s+1);
build(1,n,1);
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,b,1,n,1).m[0][0]);
else change(1,n,1);
}
}