题目描述
给你一个长度为 N 的、由 0 和 1 构成的字符串 S。
你可以执行以下操作若干次:
- 选择一个整数 1≤i≤N,Si←1−Si。
你的目标是让 S 中的 1 连续或 S 中不存在 1。请找出需要的最小的操作次数。
多组数据。
输入格式
多组数据。第一行一个整数 T(1≤T≤2×104),表示数据组数。
对于每组数据:
第一行一个整数 N(1≤N≤2×105)。
第二行一个长为 N 的字符串 S。S 由 0 和 1 构成。
保证单个测试点中 ∑N≤2×105。
输出格式
对于每组数据,输出一行一个整数表示答案。
输入输出样例
输入 #1
3 5 10011 10 1111111111 7 0000000
输出 #1
1 0 0
输入 #2
5 2 01 10 1000010011 12 111100010011 3 111 8 00010101
输出 #2
0 2 3 0 2
通过观察和手玩样例可以发现,对于任意区间 [l, r) 可以分为区间内和区间外的操作:
区间内:将区间内的 0 都转换为 1,
区间外:将 1 转换为 0。
总操作次数 =(区间长度)+(总的数字 1 的个数)- 2 *(区间内 1 的个数)。
因此我们可以想到用前缀和的方法来维护:
用 a[i] 维护字符串前 i 位中 1 的个数,
用 b[i] 维护 b[i] = i - 2 * a[i]。
此时总操作次数为 cnt+b[r]−b[l],但复杂度 O(n^2) 仍无法通过。注意到我们可以固定右端点 r,并选择最优的左端点 l 来优化。
具体地:
构建后缀最小值数组 c[i],其中 c[i]=minj≥ib[j]。
枚举左端点 l,利用 c 数组快速得到最小化的 b[r]−b[l] 值。
这样可将复杂度优化至 O(n),遍历一遍即可求出答案。代码如下:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<unordered_map>
using namespace std;
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define int long long
#define MAXN 1000100
int a[MAXN],b[MAXN],c[MAXN];
void solve(){
int n;
cin>>n;
for(int i=0;i<=n+5;i++)a[i]=b[i]=c[i]=0;
string s;
cin>>s;
s=" "+s;
for(int i=1;i<=n;i++){
if(s[i]=='1')a[i]++;
a[i]+=a[i-1];
}
int cnt=a[n];
for(int i=1;i<=n;i++)b[i]=i-2*a[i];
for(int i=1;i<=n;i++)c[i]=b[i];
for(int i=n-1;i>=0;i--)c[i]=min(c[i+1],b[i]);
int mi=0x3f3f3f3f;
for(int i=0;i<=n;i++){
mi=min(mi,cnt+c[i]-b[i]);
}
cout<<mi<<endl;
}
signed main(){
ios;
int t=1;
cin>>t;
while(t--)
solve();
return 0;
}
第一次发博客,有什么不好的还请各位大佬指点指点