AtCoder AT_abc408_d [ABC408D] Flip to Gather
题目大意
给定一个长度为 N N N 的字符串 S S S,只由 0
和 1
组成。现在,反转其中的一些位置,使字符串中要么没有 1
,要么所有的 1
都在一个连续的子段内,求最少操作次数。
思路
这是一个决策的问题,我们考虑字符串 10011
,有如下几种选择:
- 把最左侧的一段
1
变成0
。 - 把中间的
0
变成1
。 - 把最右侧的一段
1
变成0
。
这是本题动态规划的雏形,我们来仔细考虑一下如何去做。
前置操作:把第 i i i 个连续的 1
的子段的左端点记为 p i p_i pi,将这个子段的长度记为 l i l_i li。把这样的子段个数记为 c n t cnt cnt。
状态定义:令 f i f_i fi 表示在完全不改变第 i i i 个子段的情况下,最小答案是多少。
状态转移:显然左右都得考虑。左边: f i = min { f i − 1 + p i − ( p i − 1 + l i − 1 − 1 ) − 1 , ∑ j = 1 i − 1 l j } f_i=\min\lbrace f_{i-1}+p_i-(p_{i-1}+l_{i-1}-1)-1,\sum_{j=1}^{i-1}l_j\rbrace fi=min{fi−1+pi−(pi−1+li−1−1)−1,∑j=1i−1lj};右边: f i = min { f i + 1 + p i + 1 − ( p i + l i − 1 ) − 1 , ∑ j = i + 1 c n t l i } f_i=\min\lbrace f_{i+1}+p_{i+1}-(p_i+l_i-1)-1,\sum_{j=i+1}^{cnt}l_i\rbrace fi=min{fi+1+pi+1−(pi+li−1)−1,∑j=i+1cntli}。左边与右边之和即为 f i f_i fi 的最终值。
答案:所有 f i f_i fi 的最小值。
代码
AC 记录:Submission #66340268。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;int t, n, cnt;
string s;
int p[200010];
int l[200010];
int f[200010];int main()
{cin >> t;while (t--){cin >> n >> s;s = " " + s + " ";cnt = 0;for (int i = 1; i <= n; i++){if (s[i] == '1' && s[i - 1] != '1'){p[++cnt] = i;l[cnt] = 1;f[cnt] = 1e9;}else if (s[i] == '1')l[cnt]++;}if (cnt == 0 || cnt == 1){cout << "0" << endl;continue;}f[1] = 0;int s = l[1];for (int i = 2; i <= cnt; i++){f[i] = f[i - 1] + p[i] - (p[i - 1] + l[i - 1] - 1) - 1;f[i] = min(f[i], s);s += l[i];}int ans = f[cnt];s = l[cnt];for (int i = cnt - 1; i >= 1; i--){int v = f[i + 1] + p[i + 1] - (p[i] + l[i] - 1) - 1;f[i] += min(v, s);ans = min(ans, f[i]);s += l[i];}cout << ans << endl;}return 0;
}