🏂

ABC240 C - Jumping Takahashi C++解答例

2022/02/23に公開

AtCoder Beginner Contest 240 C - Jumping TakahashiをC++で解きます。

問題

問題文をAtCoder のページより引用します。

問題文

高橋君は数直線状の座標0の位置にいます。
これから高橋君はN回のジャンプを行います。i(1 \leq i \leq N)回目のジャンプでは、正の方向にa_iまたはb_i移動します。
N回のジャンプの後に座標Xの位置にいるようにすることはできますか?

制約

  • 1 \leq N \leq 100
  • 1 \leq a_i < b_i \leq 100 \quad (1 \leq i \leq N)
  • 1 \leq X \leq 10000
  • 入力は全て整数

解答例

動的計画法で解く

N回のジャンプそれぞれについてa_i, b_iの2通りの選びかたがあるので、全探索しようとすると全部で2^{N}通りになってしまうため、全探索では解けません。

i回目のジャンプの結果は、i - 1回目のジャンプまでの結果によってのみ決まり、それ以前(i - 2回目までのジャンプ)の結果に影響されません。
このような特性を持つ問題は動的計画法で解くことができます。

DP テーブルの設計

DP テーブルを以下のように定義します。

\text{dp}[i][j]i回目のジャンプを行った後、座標jにいるようにすることができるかどうかの真偽値。ただし、0 \leq i \leq N, 0 \leq j \leq X

DP テーブルの初期条件は以下のように設定します。
まだ1回もジャンプをしていないとき、すでに座標0にいるので、i = 0かつj = 0のとき真になります。その他は偽になります。

\begin{cases} \text{dp}[0][0] = \text{True} \quad (i = 0 \land j = 0) \\ \text{dp}[i][j] = \text{False} \quad (\text{other}) \end{cases}

DP の遷移

i(1 \leq i \leq N)回目のジャンプを飛ぶとき、飛び方としては以下の2通りがあります。

  1. 今いる位置から正の方向にa_iだけ飛ぶ
  2. 今いる位置から正の方向にb_iだけ飛ぶ

a_iを選ぶとき、i - 1回目までのジャンプを行うことによって座標j - a_iに到達することが可能だったなら、i回目のジャンプによってちょうど座標jにいるようにすることができます。
同様に、b_iを選ぶとき、i - 1回目までのジャンプを行うことによって座標j - b_iに到達することが可能だったなら、i回目のジャンプによってちょうど座標jにいるようにすることができます。

したがって、DPの遷移の漸化式は以下のようになります。
真を偽で上書きしないように、結果のORを取るようにしています。

\text{dp[i][j]} = \begin{cases} \text{dp}[i][j] \lor \text{dp}[i - 1][j - a_i] \\ \text{dp}[i][j] \lor \text{dp}[i - 1][j - b_i] \\ \end{cases} \quad (1 \leq i \leq N, 1 \leq j \leq X)

プログラム実装例

C++のプログラム実装例を以下に示します。

c.cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

int main() {
  int64_t N, X;
  std::cin >> N >> X;
  std::vector<int64_t> A(N + 1);
  std::vector<int64_t> B(N + 1);
  for (int64_t i = 1; i <= N; i++) {
    std::cin >> A.at(i) >> B.at(i);
  }

  // DPテーブル
  std::vector<std::vector<bool>> dp(N + 1, std::vector<bool>(X + 1, 0));
  // 初期条件
  dp.at(0).at(0) = true;

  // DP遷移
  for (int64_t i = 1; i <= N; i++) {
    for (int64_t j = 0; j <= X; j++) {
      // i回目のジャンプでaを選ぶとき
      if (j - A.at(i) >= 0) {
        dp.at(i).at(j) = dp.at(i).at(j) || dp.at(i - 1).at(j - A.at(i));
      }
      // i回目のジャンプでbを選ぶとき
      if (j - B.at(i) >= 0) {
        dp.at(i).at(j) = dp.at(i).at(j) || dp.at(i - 1).at(j - B.at(i));
      }
    }
  }

  if (dp.at(N).at(X)) {
    std::cout << "Yes" << std::endl;
  } else {
    std::cout << "No" << std::endl;
  }

  return 0;
}

実際に提出したコードはこちら

GitHubで編集を提案

Discussion