D. Binary String Sorting
D. Binary String Sorting
You are given a binary string $s$ consisting of only characters 0 and/or 1.
You can perform several operations on this string (possibly zero). There are two types of operations:
- choose two consecutive elements and swap them. In order to perform this operation, you pay $10^{12}$ coins;
- choose any element from the string and remove it. In order to perform this operation, you pay $10^{12}+1$ coins.
Your task is to calculate the minimum number of coins required to sort the string $s$ in non-decreasing order (i. e. transform $s$ so that $s_1 \le s_2 \le \dots \le s_m$, where $m$ is the length of the string after applying all operations). An empty string is also considered sorted in non-decreasing order.
Input
The first line contains a single integer $t$ ($1 \le t \le 10^4$) — the number of test cases.
The only line of each test case contains the string $s$ ($1 \le |s| \le 3 \cdot 10^5$), consisting of only characters 0 and/or 1.
The sum of lengths of all given strings doesn't exceed $3 \cdot 10^5$.
Output
For each test case, print a single integer — the minimum number of coins required to sort the string $s$ in non-decreasing order.
Example
input
6 100 0 0101 00101101 1001101 11111
output
1000000000001 0 1000000000000 2000000000001 2000000000002 0
Note
In the first example, you have to remove the $1$-st element, so the string becomes equal to 00.
In the second example, the string is already sorted.
In the third example, you have to swap the $2$-nd and the $3$-rd elements, so the string becomes equal to 0011.
In the fourth example, you have to swap the $3$-rd and the $4$-th elements, so the string becomes equal to 00011101, and then remove the $7$-th element, so the string becomes equal to 0001111.
In the fifth example, you have to remove the $1$-st element, so the string becomes equal to 001101, and then remove the $5$-th element, so the string becomes equal to 00111.
In the sixth example, the string is already sorted.
解题思路
先给出dp的做法。
定义状态
- $f(i,0)$表示将前$i$个数字处理成非递减序列,且最后一个数字是$0$的所有操作方案的集合。
- $f(i,1)$表示将前$i$个数字处理成非递减序列,且最后恰好有一个$1$的所有操作方案的集合。
- $f(i,2)$表示将前$i$个数字处理成非递减序列,且最后至少有两个连续的$1$的所有操作方案的集合。
三种状态的属性均是是操作代价的最小值。
每个状态$f(i,j), \ j \in \{0,1,2\}$根据前一次操作后的状态$f(i-1,k), \ k \in \{0,1,2\}$来转移得到。定义$M = {10}^{12}$。
- 如果$s_i = 0$,
- 对于$f(i,0)$,可以直接从$f(i-1,0)$转移过来,代价就是$f(i-1,0)$。也可以从状态$f(i-1,1)$和$f(i-1,2)$删除最后所有的$1$转移到$f(i-1,0)$,最后再接上$s_i$转移过来,代价至少为$f(i-1,0)+M+1$。
- 对于对于$f(i,1)$,无法从状态$f(i-1,0)$转移过来。可以删除$s_i$然后直接从状态$f(i-1,1)$转移过来,代价是$f(i-1,1)+M+1$,或者$s_i$与状态$f(i-1,1)$最后一个$1$交换转移过来,代价是$f(i-1,1)+M$,很明显后者更小。也可以从状态$f(i-1,2)$删除若干个$1$得到$f(i-1,1)$,然后再删除$s_i$转移过来,代价至少为$f(i-1,1) + 2 \cdot (M+1)$。
- 对于对于$f(i,2)$,无法从状态$f(i-1,0)$和$f(i-1,1)$转移过来。可以删除$s_i$然后直接从状态$f(i-1,2)$转移过来,代价就是$f(i-1,2)+M+1$。
- 如果$s_i = 1$,
- 对于$f(i,0)$,可以删除$s_i$然后直接从状态$f(i-1,0)$转移过来,代价是$f(i-1,0)+M+1$。也可以从状态$f(i-1,1)$和$f(i-1,2)$删除最后所有的$1$转移到$f(i-1,0)$,然后再删除$s_i$转移过来,代价至少为$f(i-1,0) + 2 \cdot (M+1)$。
- 对于对于$f(i,1)$,可以直接从$f(i-1,0)$转移过来,代价就是$f(i-1,0)$。也可以从状态$f(i-1,1)$和$f(i-1,2)$删除最后若干个$1$转移到$f(i-1,1)$,然后再删除$s_i$转移过来,代价至少为$f(i-1,1) + 2 \cdot (M+1)$。
- 对于对于$f(i,2)$,无法从状态$f(i-1,0)$转移过来。可以直接从状态$f(i-1,1)$和$f(i-1,2)$转移过来,代价就是$\min \{ f(i-1,1), f(i-1,2) \}$。
综上所述,状态转移方程为:
\begin{align*}
&{s_i = 0: \begin{cases} f(i,0) = f(i-1,0) \\ f(i,1) = f(i-1,1) + M \\ f(i,2) = f(i-1,2) + M + 1 \end{cases}} \\\\
&{s_i = 1: \begin{cases} f(i,0) = f(i-1,0) + M + 1 \\ f(i,1) = f(i-1,0) \\ f(i,2) = \min\{f(i-1,1), \ f(i-1,2)\} \end{cases}}
\end{align*}
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 3e5 + 10; 7 const LL M = 1e12; 8 9 char s[N]; 10 LL f[N][3]; 11 12 void solve() { 13 scanf("%s", s + 1); 14 int n = strlen(s + 1); 15 for (int i = 1; i <= n; i++) { 16 if (s[i] == '0') { 17 f[i][0] = f[i - 1][0]; 18 f[i][1] = f[i - 1][1] + M; 19 f[i][2] = f[i - 1][2] + M + 1; 20 } 21 else { 22 f[i][0] = f[i - 1][0] + M + 1; 23 f[i][1] = f[i - 1][0]; 24 f[i][2] = min(f[i - 1][1], f[i - 1][2]); 25 } 26 } 27 printf("%lld\n", min({f[n][0], f[n][1], f[n][2]})); 28 } 29 30 int main() { 31 int t; 32 scanf("%d", &t); 33 while (t--) { 34 solve(); 35 } 36 37 return 0; 38 }
还有一种贪心的做法。
可以发现在由$0$和$1$构成的序列中,如果逆序对恰好为$1$,那么我们只需要执行一次交换操作就可以了。否则逆序对的数量超过$1$个,意味着至少存在一个数需要至少交换$2$次,因此直接删除这个数最优。这意味着我们最多只执行一次交换操作,其余的都执行删除操作。
枚举分界点$i$将序列$s$分成前部分和后部分,要将$s$变成非递减序列,首先很容易想到把前部分$s[1 \sim i]$中所有的$1$删除,把后部分$s[i+1, n]$中所有的$0$删除。而只有当$s_i = 1, \ s_{i+1} = 0$时我们才交换$s_i$和$s_{i+1}$,此时代价就是$M$,而删除这两个元素的代价是$2 \cdot (M+1)$,明显交换会更优。而其他位置的交换呢?这时对于前部分在$j \in [1,i-1]$位置的元素,要将$j$位置上的元素交换到后部分的位置至少需要$2 \cdot M$,很明显不如直接删除来的更优。同理对于后部分在$j \in [i+2,n]$位置的元素,直接删除会更优。
因此直接预处理$s$前缀中$1$的个数$f(i)$和后缀中$0$的个数$g(i)$,当枚举到分界点$i$,此时变成非递减序列的最小代价就是
$$\min \left\{ \left( f(i)+g(i) \right) \cdot (M+1), \ \left( f(i)+g(i)-2 \right) \cdot (M+1) + M \right\}$$
注意只有$s_i = 1, \ s_{i+1} = 0$才能交换,即才有$\left( f(i)+g(i)-2 \right) \cdot (M+1) + M$。
最后还要考虑前部分为$s[1 \sim n]$而后部分为空,和后部分为$s[1 \sim n]$而前部分为空的情况,这里的处理方法可以看代码实现。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 3e5 + 10; 7 const LL M = 1e12; 8 9 char s[N]; 10 int f[N], g[N]; 11 12 void solve() { 13 scanf("%s", s + 1); 14 int n = strlen(s + 1); 15 f[0] = g[n + 1] = 0; 16 for (int i = 1; i <= n; i++) { 17 f[i] = f[i - 1]; 18 if (s[i] == '1') f[i]++; 19 } 20 for (int i = n; i; i--) { 21 g[i] = g[i + 1]; 22 if (s[i] == '0') g[i]++; 23 } 24 LL ret = 1e18; 25 for (int i = 0; i <= n; i++) { 26 ret = min(ret, (f[i] + g[i + 1]) * (M + 1)); 27 if (s[i] == '1' && s[i + 1] == '0') ret = min(ret, (f[i] + g[i + 1] - 2) * (M + 1) + M); 28 } 29 printf("%lld\n", ret); 30 } 31 32 int main() { 33 int t; 34 scanf("%d", &t); 35 while (t--) { 36 solve(); 37 } 38 39 return 0; 40 }
参考资料
Educational Codeforces Round 145 (Rated for Div. 2) A - D:https://zhuanlan.zhihu.com/p/616545904
Educational Codeforces Round 145 Editorial:https://codeforces.com/blog/entry/114300
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17250432.html