みずまずぷろぐらみんぐ日記

日々学んだことや頭に浮かんだことを、そこはかとなく書き連ねます

AOJ登録、そしてscanfの罠にはまる…

どうも、みずまずです。


この記事に載ってる問題もだいたい解き終えて、残すところあと数問になりました。


以前取り上げたdrkenさんのtipsの記事の方で紹介されていたこちらの記事↓を最近読んだのですが

qiita.com


そう言えばAOJって聞いたことはあっても、使ったことなかったんですよね。
レッドコーダーさんがこんなに勧めているのだから、これはやるしかない…。


ということで、ここ1週間はABCの過去問と同時進行でAOJのコースにも取り組んでいました。

AOJに登録しました

AtCoderと微妙に仕様が違って出力の最後に改行必須だったりするので、チュートリアルHello Worldで2度もPresentation Errorになったりはしましたが、何だかんだで毎日4問くらいずつ進めて昨日、最初のコース(Introduction to Programming Ⅰ)が一通り終わりました。(※競プロとあまり関係ないらしい最後の4問除く。)


やったね!

これで茶コーダーへ一歩近づけてたら良いな←


実は取り組む前は

"APG4bで競プロ初心者に必要なc++の知識は学んだしABSも解いたしな~。やる意味あるでござるか~?"

となど思っていたのですが(おい)、結果やってみて良かったです。


このコース、問題によっては日本語表示にすると問題文の上下にnoteという欄があったりして、そこのヒントボタンや言語名が書かれたボタンを押すと説明に飛ぶんですね。
そこで必要な知識やどうやって解いていくかを教えてくれる。

最初の頃は"ヒントだとぉ?俺はそんなもの見ないぞ!!"という感じだったのですが、そこを読まないとほんとに問題解くだけになってしまうので、勉強し始めの人は先に見るのが嫌なら解いた後にでも目を通しておくと良いかもです。
APG4bではさらっと流されていたcの配列が普通に使われていたり、競プロの問題を解くときに便利な関数も紹介されていました。


それから問題ページの右上、提出アイコンの並びに吹き出しアイコンのDiscussion(AOJ Board?AOJ Thread?呼び方がいまいちわかりませんが)があって、問題について質問したり、議論したり、トラブルの報告もできるようになっています。
私は40問中1問だけどうしても詰まった問題があったのですが、質問を立てたところ親切な方から即行で回答をいただけてとても助かりました。

自分で考え、調べ、解決することは大切だと思いますが、ひとりでは抜け出せなくなった時に助けてもらえる仕組みがあるというのは凄く良いなと思いました。


そしてこれが私が感じたAtCoderとの1番の大きな違いなんですが、AOJの問題は1回の入力で複数のテストケースを突っ込んでくるものがまあまああります。
当然こちらでどこからどこまでが1まとまりのテストケースなのか、どこが入力の終わりなのか判断できるようにコードを書き、複数の結果を出力しなければいけません。

(もしかしたらAtCoderもARC、AGC、企業企画のコンテストとかだとそういうケースもあるのかもしれませんが、ABCのA~D問題しかやってこなかった私にとっては結構衝撃でした…。)
1つずつ入れてテストしてくれるABCはなんて優しくて親切な奴だったんだ………。
私は甘ちゃんだった…。


この仕組みのせいおかげで、while文をめちゃくちゃたくさん書きました。
あととにかく入出力の数が多いので、cin coutで>> <<を打つのがめんどくさすぎて、APG4bで習ったものの今まで1度も使わなかったscanf関数とprintf関数を使えるようになりました。
一生cin,coutだけで生きていけると思ってたのにな…。
案外scanf,printfの方が便利な時あるんですね。良い訓練になりました(*´ω`*)


ただこのscanfにはめらることになろうとは、この時のみずまずは思ってもいなかったのである。

Finding Missing Cards

先程"どうしても詰まった問題"があったと言いましたがそれがこれです。
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_B&lang=ja


俺の考えたコードは間違っていない!絶対にだ!

……という頑固おやじ風の自信と裏腹に何度やってもWA。


途中までは良いのに、何か出力が余分に出てくる。

ちなみにその時のコードがこれ。
強い人は、あー(ここね)wって思うんでしょうか…?

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

int main() {
  int n;
  scanf("%d", &n);
  vector<vector<int>> v(4, vector<int>(14, 0));
  //key=>0=S,1=H,2=C,3=D value=>存在すれば1
  v.at(0).at(0) = 1;//数字を合わせるための調整
  v.at(1).at(0) = 1;
  v.at(2).at(0) = 1;
  v.at(3).at(0) = 1;
  for(int i = 0; i < n; i++) {
    char mark = ' ';
    int num = 0;
    scanf("%c %d", &mark, &num);
    if(mark == 'S') v.at(0).at(num) = 1;
    else if(mark == 'H') v.at(1).at(num) = 1;
    else if(mark == 'C') v.at(2).at(num) = 1;
    else v.at(3).at(num) = 1;//mark == 'D'
  }
  for(int i = 0; i < 4; i++) {
    char mark = (i == 0)? 'S'
      : (i == 1)? 'H'
        : (i == 2)? 'C'
          : 'D';
    for(int j = 0; j < 14; j++) {
      if(v.at(i).at(j) == 0) printf("%c %d\n", mark, j);
    }
  }
}


この問題は結局自分の力では直せなくてDiscussionで教えてもらい解決するのですが、scanf関数についてよく知らないまま使っていた私がまんまと罠にはまっていただけでした(チーン)

下のページ↓は回答してくれた方が載せてくださったものです。
rainbow.pc.uec.ac.jp

scanf関数では,正しく入力しても最後の改行は入力バッファに残るため,2度目以降の入力に%c指定をした場合,おかしな動作になります。


scanfでchar型を入れようとすると、改行を飛ばさず拾っちゃうんですね。
半角スペースとタブは飛ばしてくれるのに…(謎)


ということで、直すのはscanf("%c %d", &mark, &num);の部分ですね。
回避方法4を使って、scanf(" %c %d", &mark, &num);にしたところ無事AC。

たった半角スペース1つで上手く動くようになるなんて……。


あと知らなかったんですが、配列に入れる場合は'&'いらないんですね。
てかそもそも配列に一気に入れられるんですね。
おまけにAPG4bではscanfで直接文字列は入れられないって習った気がするんですが、文字列もOKなんですね(配列みたいなもんだしそういう感じ?)。


この問題のおかげでscanf関数やprintf関数について細かく知ることができましたし、よくわかっていないまま新しい構文や関数を使うのも危険だなと実感しました。(チャンチャン♪)


今回も最後まで読んでいただきありがとうございました。