【前后缀分解】AtCoder ABC393 D.Swap to Gather
题目
https://atcoder.jp/contests/abc393/tasks/abc393_d
题意
给定一个长为 \(n\) 的 \(01\) 字符串 \(s\),可以执行任意次以下操作:
- 选择两个相邻的下标,交换这两个位置上的字符,每次操作的代价为 \(1\)。
求让全部字符 \(1\) 连续的最小代价是多少?连续的含义是任意两个字符 \(1\) 之间都没有字符 \(0\)。
题解
前后缀分解,枚举每个 \(1\) 不动,让其两边全部的 \(1\) 向它紧靠的代价,这些代价中的最小值就是答案。
对于前缀,从左往右遍历,不断将 \(1\) 向右边移动。将一串 \(1\) 移动到紧靠下一个 \(1\) 的代价,是这串 \(1\) 的长度乘以这串 \(1\) 与下一个 \(1\) 之间相隔的距离(即二者之间 \(0\) 的个数)。例如:\(00110001\) 想要移动为 \(00000111\),代价便是 \(len(11) \times len(000) = 2 \times 3 = 6\)。
后缀相似于前缀的求法。
很容易想到:想要代价最小,必然至少会有个 \(1\) 是不动的。只需要枚举出每一串连续的 \(1\) 串的前后缀之和,代价最小值就是答案。
参考代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int n;
ll ans = 1e18;
string s;
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin >> n >> s;
ll cost = 0LL;
vector<ll> pre;
for (int i = 0, j = 0, k = -1; i < n; ++ i) {
if (s[i] == '0') continue;
if (k != -1) {
pre.emplace_back(1LL * (i - k) * j + cost);
cost = pre.back();
} else pre.emplace_back(0LL);
while (i < n && s[i] == '1') {
++ i;
++ j;
}
k = i;
}
if (!pre.empty()) {
cost = 0LL;
for (int i = n - 1, j = 0, k = -1; i >= 0; -- i) {
if (s[i] == '0') continue;
if (k != -1) {
cost = 1LL * (k - i) * j + cost;
ans = min(ans, cost + pre.back());
} else ans = pre.back();
pre.pop_back();
while (i >= 0 && s[i] == '1') {
-- i;
++ j;
}
k = i;
}
}
cout << (ans == 1e18 ? 0LL : ans) << '\n';
return 0;
}
浙公网安备 33010602011771号