okinawa

ITと英語の勉強メモがメイン

無課金ChatGPTにAtCoderの解説頼んでみた

AtCoderの解説読んでもよくわからない所があったので、ChatGPTに質問してみた。

最初にまとめ

  • 無課金でもChatGPTさんは強かった。
  • わからない部分を追加で質問できるのが素晴らしい。
  • 解答コードだけより問題文もつけると精度上がる。しかも問題文は整形しなくてOK!
  • 微妙に間違っているところもある(Before Afterの箇所)

質問文

◆ 命令
下記のC++ソースコードは問題文の答えを出力するソースコードです。 下記のソースコードの意味を教えてください。

◆ 問題文
縦 H マス横 W マスの 2 つのグリッド A,B があります。グリッドの各マスには # と . のいずれかの文字が書かれています。 A と B の上から i 行目、左から j 列目に書かれている文字をそれぞれ A i,j ​ ,B i,j ​ と呼びます。

次の 2 種類の操作をそれぞれ 縦方向のシフト, 横方向のシフト と呼びます。

j=1,2,…,W について次の操作を同時に行う。 A 1,j ​ ,A 2,j ​ ,…,A H−1,j ​ ,A H,j ​ を A 2,j ​ ,A 3,j ​ ,…,A H,j ​ ,A 1,j ​ に同時に置き換える。 i=1,2,…,H について次の操作を同時に行う。 A i,1 ​ ,A i,2 ​ ,…,A i,W−1 ​ ,A i,W ​ を A i,2 ​ ,A i,3 ​ ,…,A i,W ​ ,A i,1 ​ に同時に置き換える。 次の条件を満たす非負整数の組 (s,t) は存在しますか?存在する場合は Yes を、存在しない場合は No を出力してください。

縦方向のシフトを s 回行い、次に横方向のシフトを t 回行った時、操作後の A が B と一致する。 ここで、 A と B が一致するとは、 1≤i≤H,1≤j≤W を満たす整数の組 (i,j) すべてに対して A i,j ​ =B i,j ​ が成り立つことを言います。

ソースコード

#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main() {
  int H, W;
  cin >> H >> W;
  vector<string> A(H), B(H);
  for (auto& x : A) cin >> x;
  for (auto& x : B) cin >> x;
  for (int s = 0; s < H; s++) {
    for (int t = 0; t < W; t++) {
      int ok = 1;
      for (int i = 0; i < H; i++) {
        for (int j = 0; j < W; j++) {
          if (A[(i - s + H) % H][(j - t + W) % W] != B[i][j]) ok = 0;
        }
      }
      if (ok) {
        cout << "Yes" << endl;
        exit(0);
      }
    }
  }
  cout << "No" << endl;
}

ChatGPTの回答

このC++のプログラムは、2つのH行W列のグリッドAとBが与えられ、Aを上下にシフトし、次に左右にシフトすることでBと一致するかどうかを判断する問題の解法を提供します。

プログラムは、まずグリッドAとBを入力し、次にAをs回だけ上下にシフトし、t回だけ左右にシフトしてBと一致するかどうかを判断します。ここで、シフトによってグリッドの端からはみ出した部分は、グリッドの反対側に回り込むことに注意してください。

プログラムは、BとAのシフト後のグリッドが一致するかどうかを確認するために、ネストされた4つのforループを使用しています。最初の2つのループは、sとtの値を変更するためのものであり、残りの2つのループは、AとBの対応するセルが一致するかどうかを確認するためのものです。

okという変数は、現在のsとtの値に対して、AとBのシフト後のグリッドが一致するかどうかを示すフラグです。一致していない場合、okを0に設定します。最後に、okが1のままであれば、AとBが一致していることを示す"Yes"を出力し、プログラムを終了します。okが1にならない場合は、"No"を出力します。

追加の質問

シフトによってグリッドの端からはみ出した部分は、グリッドの反対側に回り込む、というのはどうやって実現していますか?

追加の回答

このプログラムでは、シフトによってグリッドの端からはみ出した部分をグリッドの反対側に回り込ませるために、C++演算子 %剰余演算子)を使っています。

具体的には、A[(i - s + H) % H][(j - t + W) % W] という部分で、%演算子を使って行と列のオフセットを計算しています。この演算は、i - s が負数になった場合でも正しいオフセットを計算するためのものです。

例えば、グリッドの高さが H=4 の場合を考えてみます。シフトを s=1 回行うと、一番上の行が一番下に移動し、他の行も上に1つずつ移動します。

Before:  After:
.#..     ....
..#.     .#..
#...     ..#.
....     ...#

この状態で、A[2][2] の要素を参照するときには、元のグリッドの A[1][2] に相当します。つまり、行のオフセットは -s になります。しかし、A[-1][2] のように負の添字を使ってしまうと、配列外参照となってしまいます。そこで、(i - s + H) % H のように、負のオフセットを正のオフセットに変換するために、% H を使って H を足してから、H で割った余りを求めます。これによって、グリッドの端からはみ出た部分が、グリッドの反対側に回り込むことができます。

列についても同様の計算を行っています。

問題文&ソースコードの引用元

B - Same Map in the RPG World

Editorial - ユニークビジョンプログラミングコンテスト2023 春 (AtCoder Beginner Contest 300)