配点: 425 点 / 実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 533
問題概要
0, 1からなる長さ の文字列
が与えられる。これから、以下の操作を0回以上行うことですべての
1がひとかたまりになった状態にしたい。
- 操作 :
を満たす整数
を選び、
と
を入れ替える。
このとき、必要な操作回数の最小値を求めよ。
制約
は整数で、
を満たす。
には
1が1つ以上含まれる。
考察
サンプルケース1や3などについて手を動かしながら考えていると、1の位置の中央値に、他の1を集めていけばよいのでは?というアイデアが思いつく。思いついちゃったんだから仕方がない...
ただ、未証明のままやるわけにもいかないので一応証明をしていく。以下では 0-indexed を前提とする。
内の左から
個目の
1のインデックスを 、
1の個数( の長さ)を
とする。また、最終的な
が
から
が
0から
が
1から
が
0
となるように操作を行うとする。
ここで、1同士を入れ替えるという操作は意味がないことを踏まえると、開始時点で の左から
番目にあった
1は、最終的に の左から
番目に移動していればよいことになる。
このときの操作回数 は、
と書くことができる。なお、数列 は単調増加数列であることに注意。
一般に、絶対偏差の和が最小となるのは (データの中央値)のときであるから(下記リンク参照)、求める最小操作回数は
となる。
(証明終)
ということで、あとはこの内容をもとに実装していこう。
実装例
#include <bits/stdc++.h> using namespace std; #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) using ll = long long; // ======================================== // int main() { int N; string S; cin >> N >> S; vector<int> pos; rep(i, 0, N) { if (S[i] == '1') pos.push_back(i); } int l = pos.size(); rep(i, 0, l) pos[i] -= i; int mid_pos = pos[l / 2]; ll ans = 0; rep(i, 0, l) ans += abs(pos[i] - mid_pos); cout << ans << endl; return 0; }
実装時間: 20分
コメント
正当性を感覚的に理解するなら、この考え方が分かりやすいかも(hibit さんありがとうございます)。
集合点が右に動くごとにコストがどう変わるかを考える場合
— hibit (@hibit_at) 2025年2月15日
自分より左にある1だけプラスされ、右にある1だけマイナスされるので、その極小値は必ずプラスとマイナスが同じ数並ぶ点になり、中央値になります