ABC472を振り返る

プログラミング

皆様こんにちは!ばるとーくです!
今回はABC472の振り返りをしていこうと思います!

結果から

垢BAN復帰後2回目となりましたが,今回の結果はなんと4完となりました!
本当に上振れでしたが...

順位表

この調子が続けば早く茶色に戻れるかもしれませんね!
とはいえ,やはり実力は足りないと思ってますので,精進に使える時間をなるべく増やしていきたいと思います~

各問題振り返り

A - A

問題文

英大文字からなる文字列 $S$ が与えられます。

$S$ のうち A 以外の文字をすべて . に置き換えた文字列を出力してください。

制約

  • $S$ は英大文字からなる長さ $1$ 以上 $100$ 以下の文字列
A - A
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

愚直に,「文字列を1文字ずつ見て,'A'だったらそのまま出力し,そうでなければ'.'を出力する.最後に改行」というコードにしました.
こんな感じです↓

cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, a, b) for (ll i = a; i < b; ++i)
#define rrep(i, a, b) for (ll i = a; i >= b; --i)

// --- 初期設定(入出力の高速化と小数15桁出力) ---
struct Init {
  Init() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout << fixed << setprecision(15);
  }
} init;
// ------------------------------------------------

int main() {
  string s;
  cin >> s;
  for (char i : s) {
    if (i != 'A') {
      cout << '.';
    } else {
      cout << i;
    }
  }
  cout << "\n";
  return 0;
}

B - Break a Stick

問題文

棒が $1$ 本あります。 この棒には切れ込みが $N-1$ 箇所入っており、切れ込みによって $N$ 個の部分に分かれています。

それぞれの部分の長さは端から順に $L_1,L_2,\dots,L_N$ です。

切れ込みを $1$ 箇所選び、そこで棒を折って $2$ 本の棒にするとき、折ってできる $2$ 本の棒の長さの差の絶対値の最小値を求めてください。

ただし切れ込みの幅は無視でき、折ってできる $2$ 本の棒の長さはそれぞれの棒に含まれる部分の長さの総和になります。

制約

  • $2 \leq N \leq 100$
  • $1 \leq L_i \leq 10^5$
  • 入力はすべて整数
B - Break a Stick
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

B問題にして $10^{5}$ とか見えたのでビビりましたが,最大操作回数ではなく入力の大きさだったので安心してよかったようです.
(流石にそこまではインフレしていなかった...)
とりあえず,「各切れ込みの長さの合計が元の長さの半分以上となったところで止める」を左からと右からの2回行い,より元の長さの半分に近いほうを採用することにしました.
こんな感じで↓

cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, a, b) for (ll i = a; i < b; ++i)
#define rrep(i, a, b) for (ll i = a; i >= b; --i)

// --- 初期設定(入出力の高速化と小数15桁出力) ---
struct Init {
  Init() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout << fixed << setprecision(15);
  }
} init;
// ------------------------------------------------

int main() {
  int n;
  cin >> n;
  vector<int> l(n);
  ll sum = 0;
  rep(i, 0, n) {
    cin >> l[i];
    sum += l[i];
  }
  ll half = sum / 2;

  ll le = 0, r = 0;
  int l_i = 0;
  rep(i, 0, n) {
    le += l[i];
    if (le >= half) {
      l_i = i;
      break;
    }
  }

  int r_i = 0;
  rrep(i, n - 1, 0) {
    r += l[i];
    if (r >= half) {
      r_i = i;
      break;
    }
  }

  if (le <= r) {
    cout << abs(le - (sum - le)) << "\n";
  } else {
    cout << abs(r - (sum - r)) << "\n";
  }
  return 0;
}

※というか無駄に最適化してる気もしなくはない(普通に $O(n^2)$ の愚直でいいのに)

C - On a Diet

問題文

高橋君は $N$ 日間の帰省で実家に滞在しています。

実家では毎日おやつが用意されており、$i$ 日目のおやつのカロリーは $A_i$ です。

高橋君は体調管理のために、直近 $M$ 日間で食べたおやつのカロリーの合計が $K$ を超えないならばおやつを食べることを繰り返します。

具体的には $i=1,2,\dots,N$ の順に、以下のルールに従って $i$ 日目のおやつを食べるかどうかを決定します。

$i=1,2,\dots,N$ それぞれについて、高橋君が $i$ 日目のおやつを食べるかどうかを判定してください。

制約

  • $1 \leq M \leq N \leq 2 \times 10^5$
  • $1 \leq K \leq 10^{15}$
  • $1 \leq A_i \leq 10^9$
  • 入力はすべて整数
C - On a Diet
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

C問題にしては比較的考察が楽だった気がします...
(個人の体感ですがね)
1日ずつ不等式評価をして,余裕があるなら食べる.ないなら食べない.
で,「$m$ 後からは,食べた合計カロリーから $m - i$ 日目のカロリーを引き算する」としました.
1点忘れていたのが,「$i$ 日目に 食べない とした場合は引き算の処理を行ってはいけない」点ですね...
(気づくのにまあまあ時間かかりましたね)
実装したコードはこちらです~↓

cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, a, b) for (ll i = a; i < b; ++i)
#define rrep(i, a, b) for (ll i = a; i >= b; --i)

// --- 初期設定(入出力の高速化と小数15桁出力) ---
struct Init {
  Init() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout << fixed << setprecision(15);
  }
} init;
// ------------------------------------------------

int main() {
  int n, m;
  ll k;
  cin >> n >> m >> k;
  vector<ll> a(n);
  vector<int> yn(n);
  rep(i, 0 , n){
    cin >> a[i];
  }

  ll cal = 0;
  rep(i, 0, n){
    if (i >= m - 1 && yn[i - m] == 0) {
      cal -= a[i - m];
    }
    if(cal + a[i] <= k){
      cal += a[i];
      yn[i] = 0;
      cout << "Yes" << "\n";
    }else{
      yn[i] = 1;
      cout << "No" << "\n";
    }
  }
  return 0;
}

D - Bomber Mad

問題文

$H$ 行 $W$ 列のグリッドがあります。各マスは、空マスまたは爆弾マスのどちらかです。上から $i$ 行目、左から $j$ 列目のマスを $(i,j)$ と表します。グリッドの情報は $H$ 個の長さ $W$ の文字列 $S_1, S_2 , \dots ,S_H$ によって与えられ、$S_i$ の $j$ 文字目が . のとき $(i,j)$ は空マス、$S_i$ の $j$ 文字目が # のとき $(i,j)$ は爆弾マスです。

また、空マス $(i,j)$ について $i$ 行目にも $j$ 列目にも爆弾マスが存在しないとき、そのマスを 安全な空マス と呼びます。

$1$ 回の移動で今いるマスから、上下左右に隣り合う空マスに移動することができます(爆弾マスには移動できません)。以下の条件を満たす空マス $(i, j)$ の数を求めてください。

制約

  • $1 \le H,W \le 5\times 10^5$
  • $H\times W \le 5\times 10^5$
  • $0 \le K \le H\times W-1$
  • $S_i$ は . と # からなる長さ $W$ の文字列
  • $H,W,K$ は整数
D - Bomber Mad
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

はい.私のだぁ~い嫌いなグリッドです.
本当に嫌そうな顔(というか声)してました(笑)

とは言い,「多始点BFS」であることは結構速く見抜いていたんじゃないかな?と思います.
しかし,如何せん実装ができない...
というわけで,先人の知恵を丸々お借りしてACするという暴挙を...
※AIじゃなくてZennに載ってる記事だから!不正じゃないよ!せっかく初4完なのに煮え切らないのはそうだけどさ!
https://zenn.dev/caselab/articles/0ee780a1524ff6

そして提出したコードがこちら↓

cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, a, b) for (ll i = a; i < b; ++i)
#define rrep(i, a, b) for (ll i = a; i >= b; --i)

#include <bits/stdc++.h>
using namespace std;

vector<vector<int>> multi_source_bfs(int H, int W, const vector<string>& grid, const vector<pair<int, int>>& starts) {
  // dist配列を初期化(-1で未訪問を表す)
  vector<vector<int>> dist(H, vector<int>(W, -1));

  // 多視点BFS用のキュー
  queue<pair<int, int>> que;

  // 全ての始点をキューに投入し、距離0で初期化
  for (auto& st : starts) {
    dist[st.first][st.second] = 0;
    que.push(st);
  }

  // 方向ベクトル(上下左右)
  int dx[4] = {1, -1, 0, 0};
  int dy[4] = {0, 0, 1, -1};

  // BFSループ開始
  while (!que.empty()) {
    auto [x, y] = que.front();
    que.pop();

    for (int k = 0; k < 4; k++) {
      int nx = x + dx[k];
      int ny = y + dy[k];

      // 範囲チェック
      if (nx < 0 || nx >= H || ny < 0 || ny >= W) continue;

      // 壁マス('#')は通れないのでスキップ
      if (grid[nx][ny] == '#') continue;

      // すでに訪問済みならスキップ
      if (dist[nx][ny] != -1) continue;

      // 未訪問で通れるマスなら距離を更新
      dist[nx][ny] = dist[x][y] + 1;
      que.push({nx, ny});
    }
  }

  return dist;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  // 入力受け取り
  int H, W, k;
  cin >> H >> W >> k;
  vector<string> grid(H);
  vector<string> fie(H);
  vector<int> safeh(H, 0);
  vector<int> safew(W, 0);
  vector<vector<int>> way(H, vector<int>(W, -1));
  for (int i = 0; i < H; i++) {
    cin >> grid[i];
  }

  rep(i, 0, H) {
    rep(j, 0, W)  {
      if (grid[i][j] == '#') {
        safew[j] = 1;
        safeh[i] = 1;
      }
    }
  }

  rep(i, 0, H) {
    if (safeh[i] == 0) {
      rep(j, 0, W) {
        if (safew[j] == 0) {
          way[i][j] = 0;
        }
      }
    }
  }

  // 複数の始点を探し、ベクターに格納
  vector<pair<int, int>> starts;
  for (int i = 0; i < H; i++) {
    for (int j = 0; j < W; j++) {
      if (way[i][j] == 0) {
        starts.push_back({i, j});
      }
    }
  }

  // 多視点BFS実行
  vector<vector<int>> dist = multi_source_bfs(H, W, grid, starts);

  // 結果出力(各マスの距離を空白区切りで表示)
  // 到達不能なマスは -1 のまま
  ll ans = 0;
  for (int i = 0; i < H; i++) {
    for (int j = 0; j < W; j++) {
      cerr << dist[i][j] << (j == W - 1 ? '\n' : ' ');
      if (dist[i][j] <= k && dist[i][j] != -1) {
        ++ans;
      }
    }
  }

  cout << ans << "\n";

  return 0;
}

最後に~レート変動~

4完したのでかなり大きく伸びましたね!
しっかし油断はできません.上振れなのはわかっているので.(無知の知)
レーティング

それでは,またお会いしましょう!
時間がある方は,おすすめ欄よりぜひほかの記事ものぞいて行ってくださいね!
また,作品集のページものぞいてみてくださいね!

宣伝

Youtubeで競技プログラミング実況を始めました!
最高実績が茶色なりたてのためなんとも稚拙な解説になっておりますが,雑魚だからこその目の付け所もあると思いますので,ぜひいらしてください~!
↓チャンネルだよ
https://www.youtube.com/@ssmbc2929bartok
2026/09/28追記:とはいえ編集さぼりすぎてろくに投稿してないよ(しくしく)

コメント

タイトルとURLをコピーしました