• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【前后缀分解】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;
}

posted on 2025-02-22 22:58  RomanLin  阅读(41)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3