michimani.net

AtCoder Beginner Contest 424 の A/B/C/D 問題の解法 #ABC424

2025-09-20

AtCoder Beginner Contest 424 の A/B/C/D 問題の解法。

実装はこちら atcoder/abc/401-500/424 · michimani/atcoder

A - Isosceles

A - Isosceles

3つの整数 \(a\), \(b\), \(c\) が与えられたとき、どの2つかが等しいかどうかを判定する問題。

実装では、3つの数値のうち任意の2つが等しい場合(\(a = b\) または \(b = c\) または \(c = a\))に “Yes” を出力し、すべてが異なる場合に “No” を出力する。

#include <iostream>

using namespace std;
using ui = unsigned int;

int main()
{
  ui a, b, c;
  cin >> a >> b >> c;

  if (a == b || b == c || c == a)
    cout << "Yes" << endl;
  else
    cout << "No" << endl;

  return 0;
}

B - Perfect

B - Perfect

\(n\) 人のうち各人が \(m\) 回正解すると特定の処理を行う問題。\(k\) 回のクエリで人と得点が与えられ、\(m\) 回正解に達した人を順番に出力する。

実装では、各人の得点を配列 \(p\) で管理し、すでに出力した人を記録するマップ \(e\) を使用している。クエリごとに該当の人の得点を増やし、得点が \(m\) に達してまだ出力していない場合、その人の番号を結果文字列に追加する。

#include <iostream>
#include <vector>
#include <map>

using namespace std;
using ui = unsigned int;

int main()
{
  ui n, m, k;
  cin >> n >> m >> k;

  vector<ui> p(n);

  ui a = 0, b = 0;
  string ans = "";
  map<ui, bool> e = {};
  for (; k--;)
  {
    cin >> a >> b;
    p[a - 1]++;
    if (p[a - 1] == m && !e[a])
    {
      ans += to_string(a) + " ";
      e[a] = true;
    }
  }

  if (ans.length() > 0)
    ans.pop_back();

  cout << ans << endl;

  return 0;
}

C - New Skill Acquired

C - New Skill Acquired

\(n\) 個のタスクがあり、各タスクは最大2つの依存関係を持つ。依存関係のないタスクから開始して、条件を満たしたタスクを順次実行していき、最終的に実行可能なタスクの総数を求める問題。

トポロジカルソートアルゴリズムの使用

実装では、トポロジカルソートの考え方を使用している。トポロジカルソートは有向非環グラフ(DAG)において、依存関係を満たしながらノードを線形に並べるアルゴリズム。

この問題でのトポロジカルソートの適用:

  1. 初期化: 依存関係のないタスク(両方の依存が0)をキューに追加
  2. BFS処理: キューからタスクを取り出し、そのタスクに依存する他のタスクをチェック
  3. 依存解決: タスクが実行可能になる条件は、2つの依存のうち少なくとも一方が満たされること
  4. 連鎖処理: 新たに実行可能になったタスクをキューに追加して処理を継続

通常のトポロジカルソートとの違いは、各タスクが2つの依存関係を持ち、OR条件(少なくとも一方が満たされればよい)で実行可能になる点。これにより、従来の入次数ベースのトポロジカルソートではなく、BFSベースの依存解決アプローチを採用している。

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int main()
{
  int n;
  cin >> n;

  vector<pair<int, int>> s(n + 1);
  vector<vector<int>> depends(n + 1);

  for (int i = 1; i <= n; i++)
  {
    cin >> s[i].first >> s[i].second;
    if (s[i].first != 0)
      depends[s[i].first].push_back(i);
    if (s[i].second != 0)
      depends[s[i].second].push_back(i);
  }

  vector<bool> l(n + 1, false);
  queue<int> q;

  for (int i = 1; i <= n; i++)
  {
    if (s[i].first == 0 && s[i].second == 0)
    {
      l[i] = true;
      q.push(i);
    }
  }

  while (!q.empty())
  {
    int current = q.front();
    q.pop();

    for (int dependent : depends[current])
    {
      if (!l[dependent])
      {
        if ((s[dependent].first == 0 || l[s[dependent].first]) || (s[dependent].second == 0 || l[s[dependent].second]))
        {
          l[dependent] = true;
          q.push(dependent);
        }
      }
    }
  }

  int ans = 0;
  for (int i = 1; i <= n; i++)
    if (l[i])
      ans++;

  cout << ans << endl;

  return 0;
}

D - 2x2 Erasing 2

D - 2x2 Erasing 2

\(h \times w\) のグリッドがあり、各セルは ‘#’ または ‘.’ で構成される。各行に対してマスクを設定し、2×2の正方形がすべて選択されないようにしながら、’#’ のセルを選択するのに必要な最小変更数を求める問題。

ビットマスクDPアルゴリズムの使用

実装ではビットマスク動的プログラミングを使用している。これは状態をビットで表現し、各ビットが特定の条件(この場合は各列の選択状態)を表すDP手法。

このアルゴリズムの適用方法:

  1. 状態定義: dp[i][mask] = \(i\) 行目でマスク mask を使った場合の最小変更数
  2. ビットマスク: 各行で \(w\) 個の列それぞれの選択状態を1ビットで表現(1 << w 通りの状態)
  3. 状態遷移: 前の行のマスクと現在行のマスクの組み合わせをすべて試行
  4. 制約チェック: 2×2正方形の制約を満たすかを隣接する2列×2行で確認
    bool tl = (prev_mask & (1 << j)) != 0;      // 左上
    bool tr = (prev_mask & (1 << (j + 1))) != 0; // 右上
    bool bl = (cur_mask & (1 << j)) != 0;       // 左下
    bool br = (cur_mask & (1 << (j + 1))) != 0;  // 右下
    
  5. コスト計算: ‘#‘セルが選択されていない場合にペナルティを加算

計算量: \(O(h \times 4^w \times w)\) となり、\(w\) が小さい場合に効率的。ビットマスクDPは状態数が指数的だが、制約が小さい問題で威力を発揮する典型的なアルゴリズム。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
  int t;
  cin >> t;

  while (t--)
  {
    int h, w;
    cin >> h >> w;

    vector<string> g(h);
    for (int i = 0; i < h; i++)
      cin >> g[i];

    vector<vector<int>> dp(h + 1, vector<int>(1 << w, 1e9));
    dp[0][0] = 0;

    for (int i = 0; i < h; i++)
    {
      for (int prev_mask = 0; prev_mask < (1 << w); prev_mask++)
      {
        if (dp[i][prev_mask] == 1e9)
          continue;

        for (int cur_mask = 0; cur_mask < (1 << w); cur_mask++)
        {
          int changes = 0;
          bool valid = true;

          for (int j = 0; j < w; j++)
          {
            if (g[i][j] == '#' && !(cur_mask & (1 << j)))
              changes++;
          }

          if (i > 0)
          {
            for (int j = 0; j < w - 1; j++)
            {
              bool tl = (prev_mask & (1 << j)) != 0;
              bool tr = (prev_mask & (1 << (j + 1))) != 0;
              bool bl = (cur_mask & (1 << j)) != 0;
              bool br = (cur_mask & (1 << (j + 1))) != 0;

              if (tl && tr && bl && br)
              {
                valid = false;
                break;
              }
            }
          }

          if (valid)
            dp[i + 1][cur_mask] = min(dp[i + 1][cur_mask], dp[i][prev_mask] + changes);
        }
      }
    }

    int ans = 1e9;
    for (int mask = 0; mask < (1 << w); mask++)
      ans = min(ans, dp[h][mask]);

    cout << ans << endl;
  }

  return 0;
}

comments powered by Disqus