DISCO presents ディスカバリーチャンネル コードコンテスト2016 本戦: C - 01文字列
問題概要
空文字列Sに対して以下の3種類の操作を好きな順番で好きな回数行い、与えられた文字列Tに一致させたい。そのための最小コストを求めよ。 1. Sの先頭に0を挿入する(コストA)。 2. Sの末尾に1を挿入する(コストB)。 3. Sの01をすべてflipする(コストC)
|T| <= 2×105
解法
操作1の回数をa回と決め打つと、操作bの回数は|T|-b回に定まる(Sの長さは操作1, 操作2によってしか増えないため)。さらに操作の特性上、先頭a文字が操作1由来、後ろのb文字が操作2由来であることも確定する。この時点でコストAはa回, コストBはb回支払うことが確定し、問題は操作3の回数をどれだけ少なくできるかという点に移る。操作3が反転であることを考えると明らかに0と1の切れ目では反転を行う必要があり、そうでないところでは必要がない。またaとbの切断箇所で隣り合う文字が同一の場合、どちらかが1回待たなければならない。とかの面倒なケースを処理しつつ計算する。これはO(1)でできるので操作1の決め打ち回数を全探索してO(N).
感想
なんかごちゃごちゃしてしまってつらかった
コード (D言語)
import std.stdio, std.array, std.string, std.conv, std.algorithm; import std.typecons, std.range, std.random, std.math, std.container; import std.numeric, std.bigint, core.bitop, std.bitmanip; void main() { auto s = readln.split.map!(to!long); auto A = s[0]; auto B = s[1]; auto C = s[2]; auto S = readln.chomp.map!(c => (c - '0').to!int).array; auto N = S.length.to!int; long ans = 1L << 59; auto seg1 = new int[](N); auto seg2 = new int[](N); seg1[0] = 1; seg2[N-1] = 1; foreach (i; 1..N) seg1[i] = S[i] == S[i-1] ? seg1[i-1] : seg1[i-1] + 1; foreach_reverse (i; 0..N-1) seg2[i] = S[i] == S[i+1] ? seg2[i+1] : seg2[i+1] + 1; foreach (a; 0..N+1) { // 先頭に追加する回数 int b = N - a; // 末尾に追加する回数 int s1 = a == 0 ? 0 : seg1[a-1]; int s2 = a == N ? 0 : seg2[a]; int flip = max(s1, s2) - 1; if (a > 0 && a < N && s1 == s2 && S[a] == S[a-1]) flip += 1; if (s1 >= s2 && S[0] == 1) flip += 1; if (s1 < s2 && S[N-1] == 0) flip += 1; ans = min(ans, a * A + b * B + flip * C); } ans.writeln; }