緑コーダーがRustで解説してみた(ABC419 A~E)
AtCoder Beginner Contest 419のA~E問題を緑コーダーが自分なりの解説と解答コードをまとめました。
参考になりましたら幸いです。
ABC419-A
問題
与えられた文字列を特定のルールに従って変換する問題です。
解説
問題文に記載された変換ルールを整理すると以下表の通りになるため、この内容に沿って変換します。
変換前の文字列 | 変換後の文字列 |
---|---|
red |
SSS |
blue |
FFF |
green |
MMM |
上記以外 | Unknown |
コード
use proconio::input;
fn main() {
// 入力
input! {
s: String, // 文字列
}
// 文字列に応じた変換後の文字列を出力
match s.as_str() {
"red" => println!("SSS"),
"blue" => println!("FFF"),
"green" => println!("MMM"),
_ => println!("Unknown"),
}
}
ABC419-B
問題
クエリの内容に従い、値の追加と今持っている値の最小値を取り出す問題です。
解説
この問題を解くのに用いるデータ構造としては、優先度付きキューを使用するのが適しています。Rustでは、 BinaryHeap
というデータ構造を使うことで、効率的に値の追加や最小値の取り出しが可能です。
ただし、 BinaryHeap
はデフォルトで最大値を優先する構造になっているため、最小値を優先するようにするには、 Reverse
ラッパーを用いて値を反転させる必要があります。
コード
use proconio::input;
use std::collections::BinaryHeap;
use std::cmp::Reverse;
fn main() {
// 入力
input! {
q: usize, // クエリ数
}
// 優先度付きキューで値を昇順で管理する
let mut hp = BinaryHeap::new();
// クエリ処理
for _ in 0..q {
input! {
nm: usize, // 番号
}
// 1の場合は、値xをキューに追加
if nm == 1 {
input! {
x: usize,
}
hp.push(Reverse(x));
}
// 2の場合は、キューから最も小さい値を取り出す
else {
if let Some(Reverse(val)) = hp.pop() {
println!("{}", val);
}
}
}
}
ABC419-C
問題
各人が上下左右斜めに1マスずつ移動(移動しないも含む)する際に、全員が同じ位置に集まるために必要な移動距離を最小化する問題です。
解説
全員が集まる位置として、
中心座標は
で求められます。
次に各人の中心座標への移動ですが、今いる座標と中心座標との距離差になるため、
これを全員分計算し、移動距離が最大のものが答えとなります。
コード
use proconio::input;
use std::cmp::max;
fn main() {
// 入力
input! {
n: usize, // 人数
rc: [(i64, i64); n], // 人の座標
}
// r軸方向、c軸方向を独立して管理
let mut rlist = Vec::new();
let mut clist = Vec::new();
for (r, c) in rc {
rlist.push(r);
clist.push(c);
}
// r軸方向とc軸方向の中心を求める
let r_center = calc_center(&rlist);
let c_center = calc_center(&clist);
// 各人について、中心座標から遠い距離差を選び、最大値を求める。
let mut ans = 0;
for i in 0..n {
let diff_r = (rlist[i] - r_center).abs();
let diff_c = (clist[i] - c_center).abs();
let val = max(diff_r, diff_c);
ans = max(val, ans);
}
// 答えを出力
println!("{}", ans);
}
// 座標軸の中心を計算する関数
fn calc_center(pos_list: &Vec<i64>) -> i64 {
// 座標軸の最小値、最大値を求める
let pos_min = *pos_list.iter().min().unwrap();
let pos_max = *pos_list.iter().max().unwrap();
// 座標軸の中心を求める
(pos_min + pos_max) / 2
}
ABC419-D
問題
文字列
解説
同じ添字
交換操作は指定された範囲でまとめて行われますが、範囲指定の加算処理を効率的に行うために、imos法を使用します。imos法では、範囲の始点に加算、終点の次の位置に減算を記録し、最後に累積和を取ることで範囲の加算を効率的に計算できます。
コード
use proconio::{input, marker::Chars, marker::Usize1};
fn main() {
// 入力
input! {
n: usize, // 文字列の長さ
m: usize, // 操作回数
s: Chars, // 文字列S
t: Chars, // 文字列T
lr: [(Usize1, Usize1); m], // 操作範囲[l, r] (0-index)
}
// 各文字の操作回数をimos法で計算
let mut imos = imos_init(n);
// 加算する区間[l, r]をセット
for &(l, r) in &lr {
imos_set(l, r, &mut imos);
}
// 加算を実行
imos_exe(&mut imos, n);
// 文字を出力
for i in 0..n {
// 操作回数が奇数ならt、偶数ならsの文字を出力
print!("{}", if imos[i] % 2 == 1 { t[i] } else { s[i] });
}
println!();
}
// imos法の初期化
fn imos_init(n: usize) -> Vec<i64> {
// 余裕を持たせて n+2 にする
vec![0; n + 2]
}
// 区間 [l, r] の変化量を設定
fn imos_set(l: usize, r: usize, cnt: &mut Vec<i64>) {
cnt[l] += 1;
cnt[r + 1] -= 1;
}
// 累積和を計算
fn imos_exe(cnt: &mut Vec<i64>, n: usize) {
for i in 0..n {
cnt[i + 1] += cnt[i];
}
}
ABC419-E
問題
数列
解説
長さ
各グループについて、どの値に揃えるかを動的計画法(DP)により選択しながら、必要な操作回数を最小化するように計算します。
DPの状態と遷移は以下のように定義します。
- 状態
※
- 遷移
※
実装方針としては、以下1~4のとおりになります。
- 数列を
個のグループに分けます。L - 各グループについて、どの値に揃えるかを選択した際に増加する操作回数を事前計算します。
- DPを用いて、グループごとに操作回数を最小化しながら進めていきます。
- 最終的に、総和の状態が
となる場合の操作回数を出力します。0
コード
use proconio::input;
use std::cmp::min;
const INF: usize = 1 << 60;
fn main() {
// 入力
input! {
n: usize, // 数列の長さ
m: usize, // Mの倍数
l: usize, // 連続部分列の長さ
a: [usize; n], // 数列の各値
}
// 数列をL個のグループに分ける
let groups = array_grouping(n, l, &a);
// 各グループで、目標値(to)に揃える際に増加する操作回数を計算
let add_cost = calc_addcost_groupby(m, l, &groups);
// DP配列の初期化(dp[総和(Mで割った余り)] = 増加する操作回数のmin)
let mut cur_dp = vec![INF; m];
cur_dp[0] = 0;
// グループごとにDPを更新
for idx in 0..l {
let mut next_dp = vec![INF; m];
// 操作前と操作後の総和を全探索
for from in 0..m {
for to in 0..m {
// idx番目のグループで選択する目標値
let adds = (to + m - from) % m;
// DP更新
next_dp[to] = min(next_dp[to], cur_dp[from] + add_cost[idx][adds]);
}
}
cur_dp = next_dp;
}
// 総和が0のときの操作回数を出力
println!("{}", cur_dp[0]);
}
// 数列をL個のグループに分ける関数
fn array_grouping(n: usize, l: usize, a: &Vec<usize>) -> Vec<Vec<usize>> {
let mut groups = vec![Vec::new(); l];
for i in 0..n {
let idx = i % l;
groups[idx].push(a[i]);
}
groups
}
// 各グループで、現在の各値(from)を目標値(to)に揃える際に増加する操作回数を計算する関数
fn calc_addcost_groupby(m: usize, l: usize, groups: &Vec<Vec<usize>>) -> Vec<Vec<usize>> {
let mut add_cost = vec![vec![0; m]; l];
for idx in 0..l {
for to in 0..m {
let mut cost = 0;
for &from in &groups[idx] {
cost += (to + m - from) % m;
}
add_cost[idx][to] = cost;
}
}
add_cost
}
Discussion