B. Ugu
B. Ugu
A binary string is a string consisting only of the characters $0$ and $1$. You are given a binary string $s_1 s_2 \dots s_n$. It is necessary to make this string non-decreasing in the least number of operations. In other words, each character should be not less than the previous. In one operation, you can do the following:
- Select an arbitrary index $1 \leq i \leq n$ in the string;
- For all $j \geq i$, change the value in the $j$-th position to the opposite, that is, if $s_j=1$, then make $s_j=0$, and vice versa.
What is the minimum number of operations needed to make the string non-decreasing?
Input
Each test consists of multiple test cases. The first line contains an integer $t$ $(1 \leq t \leq {10}^{4})$ — the number of test cases. The description of test cases follows.
The first line of each test cases a single integer $n$ $(1 \leq n \leq {10}^{5})$ — the length of the string.
The second line of each test case contains a binary string $s$ of length $n$.
It is guaranteed that the sum of $n$ over all test cases does not exceed $2 \cdot {10}^{5}$.
Output
For each test case, output a single integer — the minimum number of operations that are needed to make the string non-decreasing.
Example
input
8 1 1 2 10 3 101 4 1100 5 11001 6 100010 10 0000110000 7 0101010
output
0 1 2 1 2 3 1 5
Note
In the first test case, the string is already non-decreasing.
In the second test case, you can select $i=1$ and then $s=01$.
In the third test case, you can select $i=1$ and get $s=010$, and then select $i=2$. As a result, we get $s=001$, that is, a non-decreasing string.
In the sixth test case, you can select $i=5$ at the first iteration and get $s=100001$. Then choose $i=2$, then $s=111110$. Then we select $i=1$, getting the non-decreasing string $s=000001$.
解题思路
先说一下官方的题解。
构造一个数组$a$,其中如果$s_i = s_{i-1}$,则$a_i = 0$;否则如果$s_i \ne s_{i-1}$,则$a_i = 1$。其中$a_1 = s_1$。这就是一个异或的差分数组。
如果反转位置$i$上的数,那么区间$[i+1, n]$内的数都要反转,根据差分的性质可以发现这个操作对数组$a$的影响是只有$a_i$发生了改变($0$变$1$或$1$变$0$)。
对于一个非递降的序列,可以发现对于的数组$a$要么全是$0$,要么只有一个$1$。全$0$意味着整个$s$都是同一类字符,只有一个$1$意味着这个序列是 00 ... 01 ... 111 这种形式。
这时我们统计整个数组$a$(区间$[1, n]$)中$1$的个数。如果字符串的首字母即$s_0 = 0$,那么此时$a$中最多只能有一个$1$,其余的$1$我们都要通过操作变成$0$,因此对于这种情况的答案就$max \{ 0, cnt - 1 \}$。如果字符串的首字母即$s_0 = 1$,那么此时$a$中只能含有一个$1$(也就是首字符的$1$),其余的$1$(区间$[2, n]$)我们都要通过操作变成$0$,因此对于这种情况的答案就$cnt - 1$。
补充,这题是怎么想到用差分的呢。实际上,当区间$[i, n]$内的数反转时,等价于对这个区间内的每一个数进行模$2$加法(加$1$再对得到的值模$2$),因此就是对某个区间内的每个数加上一个数的问题,因此想到差分。由于最终整个序列是非递降的,因此对应的差分数组应该最多包含一个$1$,而每次对区间$[i, n]$进行模$2$加法,等价于对应的差分数组的第$i$个位置进行模$2$加法(第$n+1$个位置可以忽略)。因此直接统计原始序列的差分数组中多余的$1$的个数就可以了。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 2e5 + 10; 5 6 char str[N]; 7 8 void solve() { 9 int n; 10 scanf("%d %s", &n, str + 1); 11 12 int cnt = 0; 13 for (int i = n; i; i--) { 14 cnt += (str[i] & 1) ^ (str[i - 1] & 1); 15 } 16 17 int ret; 18 if (str[1] == '0') ret = max(0, cnt - 1); 19 else ret = cnt - 1; 20 21 printf("%d\n", ret); 22 } 23 24 int main() { 25 int t; 26 scanf("%d", &t); 27 while (t--) { 28 solve(); 29 } 30 31 return 0; 32 }
另外一种解法是参考其他人的。
对于字符串$s$我们进行去重操作,比如有${\color{Red}{11}}{\color{Blue}{0}}{\color{Red}{1}}{\color{Blue}{000}}{\color{Red}{1}}{\color{Blue}{0}}{\color{Red}{11}}$,那么去重后就是$1010101$,即把连续相同的一段都用这一段中的首个数字来表示,统一把操作归到首个数字。
可以发现去重后得到的结果只有两种,要么是从$0$开始的$01$交替序列,要么是从$1$开始的$01$交替序列。
对于从$0$开始的序列,很明显最优解是从第$3$个位置开始每一个位置都置换一次,因此答案为$n-2$。
对于从$1$开始的序列,先考虑置换第$1$个位置,那么就会得到从$0$开始的$01$交替序列,接着从第$3$个位置开始置换后面每一个位置,因此这种方式的最优解为$1 + n - 2 = n - 1$。如果不置换第$1$个位置,那意味着整个序列要均为$1$,那么从第$2$个位置开始每个位置都要置换,因此答案就是$n - 1$。可以发现这两种方式得到的最优解是一样的,那么我们规定只要遇到$1$那么就对这个位置上的数进行置换。
在实际实现中我们并不需要把去重的结果求出来,直接枚举原字符串就可以了,只要遇到$1$就进行置换。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 2e5 + 10; 5 6 char str[N]; 7 int s[N]; 8 9 void solve() { 10 int n; 11 scanf("%d %s", &n, str + 1); 12 memset(s, 0, sizeof(s)); 13 for (int i = n; i; i--) { 14 s[i] = s[i + 1] + (str[i] & 1); // 求后缀1个个数 15 } 16 17 int ret = 0; 18 for (int i = 1, t = 0; i <= n; i++) { // t代表置换的累加次数 19 if (!t && s[i] == n - i + 1) break; // 如果置换了偶数次,并且[i, n]中全为1,那么就不用再置换了 20 if (t && !s[i]) break; // 如果置换了奇数次,并且[i, n]中全为0(等价于置换后全为1),也不用置换了 21 if ((str[i] & 1) ^ t) ret++, t ^= 1; // 遇到1就置换 22 } 23 24 printf("%d\n", ret); 25 } 26 27 int main() { 28 int t; 29 scanf("%d", &t); 30 while (t--) { 31 solve(); 32 } 33 34 return 0; 35 }
参考资料
Codeforces Round #830 (Div. 2) Editorial:https://codeforces.com/blog/entry/108327
Codeforces Round #830 (Div. 2)A、B、C1、D1(C2、D2待补):https://zhuanlan.zhihu.com/p/576542239
差分的应用:https://www.cnblogs.com/onlyblues/p/16282454.html
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16821610.html