リブ的おすすめの本01 c/c++編 人工知能もあるよ!
リブがおすすめする本シリーズ第一弾
- C++プライマー
- C言語による標準アルゴリズム辞典
- C++で学ぶディープラーニング : ニューラルネットワークの基礎からC++による実装まで
- コンピューターで「脳」がつくれるか : AIが恋に落ちる日
- 紹介した本の表紙
僕が今まで読んだ本の中で、良かった本の備忘録を作ると同時に共有したいと思います。
学生は勿論ですが、大人のエンジニアの方にも読んだことがなければ読んでみてほしいと思います。
以下に述べるのは学術本やプログラミングの本です。((大)学生に薦めたい参考書も)
自己啓発本やお金、ビジネス本などの本はいろんな人がレビューしてますし、自分に必要な分を中学生の頃には見終わってますので(高校生になってもたまに探した気もします)、古すぎてわざわざ書く気が起きないです。
新しい本とかも、よっぽど目を引いたり薦められたりしない限り読まないと思います。
自己啓発本を毛嫌う人もいますけど、自分は当時読んだことで色々試して何が本当に役立って何が役に立たないのかというのを見極めることができました。そのおかげで、今の自分がありますし、非常に役に立っていると思います。
その辺の話も興味がおありでしたらコメントください。
C++プライマー
c++の規格書リーディングする前段階にこれが良いと思います。非常に読みごたえがありますが、その分中身は充実しています。例が多いのと解説も多いので900ページを超える内容です。
気を付ける点としては、C++11までの内容であること。意外と誤植?間違え?があります。あと、ところどころ読みにくい。(和訳のせい?)
ですが、有志の方が解答や解説や誤植をまとめてくださってるのでカバーできます。(英語です。リンクはまぁ貼らんでええかなって(ずぼら)ggrのが下手なエンジニアなんておらんやろ(慢心))
しかし、それを差し引いても余りある魅力満点の本だと思います。 なんで規格書リーディング前におすすめかというと型推論の中身とかについて触れるなどしてるので規格書読むときに少し内容が入ってきやすいんじゃないかなというのが理由。
対象としては、「別の言語が分かる人」または「何かの言語のプログラミングの入門書を読んだことがあり、変数などの基本用語を理解していて、実際にコード(簡単でいい)を書いたことがある人」だと思います。
いきなりこの本で学習することは無謀だと思います。たいてい簡単なことから始めないとつぶれちゃうので💦
ちなみに、先程「コードを書いたことが」と言いましたが、書いたことがなくても大丈夫です。正直。なぜなら、C++プライマーが簡単なコードを最初の方に書いてくれるからです。さらに、二人三脚みたいな感じで最初は進んでいくので割と行けます。
途中詰まったら自分がそのレベルの機能が必要なとこまで行ってないとか、書いたことのあるコードが少ないとかが原因だと思うので、一回そこだけ飛ばして読んだり、暫くしてから読んだりするのが良いと思われます。
C言語による標準アルゴリズム辞典
神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神神 of 神だと思います。言葉はいらない。
だと意味わかんないんで説明しますと、アルゴリズムをひたすらC言語で書いてくれてます。そして分かりやすい。それだけ。。。💦 そこがいいんですけどね。
C++で学ぶディープラーニング : ニューラルネットワークの基礎からC++による実装まで
これはamazonのレビューで書かれていることが全てです。書かれている通り名著になれるポテンシャルがあるんですけどねー。残念。
あと、P32図3-2が右肩下がりの直線になっているのが多分間違ってると思います。他にも色々あるのですが、amazonレビュー見てみてください。
しかし、実はこの本にはレビューには書かれていないもう一つの魅力があるんです。これ、人工知能について凄くよくまとめられているので人工知能に関するレポート課題が出されたときに強い味方になります!!!(実体験)
コンピューターで「脳」がつくれるか : AIが恋に落ちる日
これは人工知能に関するレポート繋がりですね。これからの時代についてだったり、人間の脳の仕組みについても述べています。非常に読みやすい本ですので一度読んでみてはいかがでしょうか。
教養的にもおすすめです。
紹介した本の表紙
AtCoderの良解答からみるプログラミングのテクニック06(ビギナー向け) C++
このシリーズ第一回
hajimekata-nyumon-donyu.hatenablog.com
前回
hajimekata-nyumon-donyu.hatenablog.com
drken(けんちょん)さんの記事の進行で進みます。
こんにちは、今回も学んだことを共有させていただきます。
途中に他の方のコードを引用しますが、そのコードの著作権はそのコードを書いた方が有しています。
元のコードが見たい場合は
今回から第九回までは問題名 提出#〇〇〇〇〇
で検索をすれば見ることができます。提出#〇〇〇〇〇
のかたちで紹介させていただきます。が十回目からはリンクを張ることにしました。検索してもでず、地道に番号から探さないと見つけられないみたいだからです。
※注 スマホだとコードは見にくいのでPCモードかPCで見たほうが良いです。
At Corder で見つけた解答から得たもの
・ABC090_B
提出 #4568846より
signed main(void) { int a, b, cnt = 0; string s, front, end; cin >> a >> b; REP(i, a, b + 1) { s = to_string(i); front = s.substr(0, 2); end = s.substr(3, 2); reverse(all(end)); if (front == end) { cnt++; } } cout << cnt << endl; return 0; }
前回学んだto_string( )が使われていますね。忘れた方は前回の記事で復習できます。 が、簡単に説明を書いておきますね。 to_string()は数字を引数とする関数です。to_string('数字')と書きます。 すると、引数を文字列に直した結果を戻り値として返してくれんでしたね。 そして、どうやら見慣れないsubstr( )というものがありますね。 これは以下のように書くことができます。
const std::string s = "hello"; // 2番目から3要素だけ抜き出した部分文字列を取得する { std::string result = s.substr(2, 3); std::cout << result << std::endl; //出力結果 ‘llo‘
substr();は第一引数に‘‘‘どこから‘‘‘という情報を与えます。次に、第二引数で‘‘‘何要素文‘‘‘抜き出すかという情報を与えます。そうすることで、今回の例の場合hello
の二番目の要素であるl(エル)
から3文字分抜き出した部分文字列を取得できます。
次に、youtubeの解説と同じ解法を紹介します。
signed main(){ int a, b; cin >> a >> b; int cnt = 0; FOR(i,1,10){ REP(j,10){ REP(k,10){ int x = 10000*i+1000*j+100*k+10*j+i; if(x >= a and x <= b) ++cnt; } } } cout << cnt << endl; return 0; }
これは10001以上99999以下の数字の中にあるA以上B以下という条件を満たす回文が存在するならカウント変数をインクリメントしてくださいね。というコードです。 10000も問の条件に含まれますが回文でないことは自明なので10001~99999の間で探索できます。 ちなみに、
int x = 10000*i+1000*j+100*k+10*j+i;
これは回文を表すことは自明ですよね。
提出 #4581500より
int main() { int A, B; cin >> A >> B; int ans = 0; for (int i = A; i <= B; i++) { string s; s = to_string(i); if (s[0] == s[4] && s[1] == s[3]) ans++; } cout << ans << endl; return 0; }
これは、回文であれば整数ijkmn(i,j,k,l,m,nはそれぞれ桁を表す。例12345)ではなく、ijkji(例12321)のようになっていることを利用しています。先程の解答に近いですね。 to_string( )によってint型をstring型に変換してやって0番目の要素と4番目の要素が等しく1番目と3番目の要素が等しいならば回文である。 というロジックですね。 次は僕の解答です。先程の解答を整数のまま再現した場合と考えていいです。はじめに、関数を定義しています。この関数の戻り値は回文の個数。つまり解です。
int kai(int i){ int cnt = 0; int one = i % 10; //一桁目 int ten = (i / 10)%10; //二桁目 int tho = (i / 1000)%10; //三桁目 int man = (i / 10000); //四桁目 if(one == man && ten == tho) //一桁目==四桁目 && 二桁目==三桁目 ++cnt; return cnt; } signed main() { int a,b; cin >> a >> b; int ans = 0; FOR(i, a, b+1) ans += kai(i); cout << ans; return 0; }
このシリーズ第5回、つまり前回の時に学んだ10進法表記が分かっていれば理解できるコードですね。一桁目,二桁目,三桁目,四桁目をそれぞれ求めてやって、それが回文の性質である、ijkjiを満たしているかを判別するというコードです。 10進法表記の復習をしましょう。 整数の10進法表記の処理として、一桁ずつ処理をするなら%10をすることで最下位桁を着目することができ、元の整数を/=10することで着目している桁を一つ左にずらせるんでしたね。
さいごに
今回はto_string()と10進法表記の復習ができました。さらに、新しいこととしてsubstr( );の使い方を解説しました。これで部分文字列の問題が一部解けるようになったはずです。 以上!ABC090の解説でしたー。
AtCoderの良解答からみるプログラミングのテクニック05(ビギナー向け) C++
このシリーズ第一回
hajimekata-nyumon-donyu.hatenablog.com
前回
hajimekata-nyumon-donyu.hatenablog.com
drken(けんちょん)さんの記事の進行で進みます。
こんにちは、今回も学んだことを共有させていただきます。
途中に他の方のコードを引用しますが、そのコードの著作権はそのコードを書いた方が有しています。
元のコードが見たい場合は
今回から第九回までは問題名 提出#〇〇〇〇〇
で検索をすれば見ることができます。提出#〇〇〇〇〇
のかたちで紹介させていただきます。が十回目からはリンクを張ることにしました。検索してもでず、地道に番号から探さないと見つけられないみたいだからです。
※注 スマホだとコードは見にくいのでPCモードかPCで見たほうが良いです。
At Corder で見つけた解答から得たもの
・ABC083_B
問)1以上N以下の整数のうち、10進法での各桁の和がA以上B以下であるものの総和を求めてください。
提出 #4549595より
int main(){ int n,a,b; cin>>n>>a>>b; int ans=0; for(int i=1;i<=n;++i){ string s=to_string(i); int sum=0; for(int j=0;j<s.size();++j){ sum+=s[j]-'0'; } if(a<=sum&&sum<=b)ans+=i; } cout<<ans<<endl; }
これは1以上N以下の範囲で1,2,3,4,...,25,...,nのそれぞれに対して文字列に変換し、その変換した文字列の各桁の和をsum+=s[j]-'0';
によって求めている。
s[j] - '0' は何を表しているのでしょうか。まずs[j] を確認しましょう。s は与えられた整数の文字列です。s[j] はその文字列のj 番目の文字でした。つまりs[j] は0~9のどれかが文字として出力されることになります。
ということは、例えば '9' -'0' のような計算になるということです。では、その計算結果はどうなるのでしょうか。答えは9になります。'数字' - '数字' をするとその数字が後ろの数字からどれだけ離れているかを出力することになります。'5' - '2' であれば '3' です。
これで、sum += s[j] - '0' の意味が分かりましたね。s[j] の文字列になっている数字をint型として出力してくれるようになっています。
そして、各桁の和を求めるたびに範囲を確認して条件を満たす数字の数を数えているんですね。
最後にもう一度、先ほどのコードを見てみましょう。理解できるのではないでしょうか。
int main(){ int n,a,b; cin>>n>>a>>b; int ans=0; for(int i=1;i<=n;++i){ string s=to_string(i); int sum=0; for(int j=0;j<s.size();++j){ sum+=s[j]-'0'; // 123なら sum = 1+2+3; } if(a<=sum&&sum<=b)ans+=i; } cout<<ans<<endl; }
Tips02_to_string()は数字を文字列に変換する関数
もちろん、逆もある。stoi( )だ。(恐らく、string_to_intの意)
これらはそれぞれ、to_string('数字'), stoi('文字')のように書く。
to_string()もstoi()もそれぞれ引数を変換した結果を戻り値として返してくれる。
ちなみに自分の解答は
signed main() { int n,a,b; cin >> n >> a >> b; int ans = 0; for(int i = 1; i <= n; ++i){ int sum = 0; int x = i; for(int j = 0; j < 5; ++j){//たかだか5桁(10000)なので五回 sum += x % 10; //一の位が求まる。 x /= 10; //139 becomes 13 } if(a <= sum && sum <= b) ans += i; } cout << ans; return 0; }
整数の10進法の表記を考えて答案しました。処理の流れはコメントに書いてあるので恐らく大丈夫だと思います。 このように、10進法を考えてやれば整数を一桁一桁考えてやることができます。 まず、10で割ったあまりを考えることで整数の1の位が何なのかを取得します。
x % 10 → x の一桁目 : 1 % 10 → 1 なぜなら 1 = 0×10 +1 2 % 10 → 2 なぜなら 2 = 0×10 +2 34 % 10 → 4 なぜなら 34 = 3×10 +4 456 % 10 → 6 なぜなら456 = 45×10 + 6
次に、それを何らかの処理(今回ではsum+=)をしてやった後に、x /= 10;によって 一桁ずらしています。(ずらした分は消えます。)後は範囲を満たしていれば出力するだけです! (あれ?これけんちょんさんと同じアルゴリズムじゃね?まぁ、そりゃけんちょんさんの記事で勉強してるからそうなるけど。 まぁ他の方の解答だけだったら怒られるから自分の解答を必ず付けることにしてるから仕方がないっちゃ仕方がないけど解説も似たようなもんじゃん...と思ったけどまぁ2回見ることによってより理解深まるよね。あと、これ日記だからセーフだから(迫真)あと、僕の備忘録というか理解のためでもあるし...)
さいごに
今回はto_string()によって数字を文字列にする方法を学びました。さらに、整数の10進法表記の処理として、一桁ずつ処理をするなら%10をすることで最下位桁を着目することができ、元の整数を/=10することで着目している桁を一つ左にずらせることを学べたのではないでしょうか。 以上!ABC 102とABC113の解説でしたー。
AtCoderの良解答からみるプログラミングのテクニック04(ビギナー向け) C++
このシリーズ第一回
hajimekata-nyumon-donyu.hatenablog.com
前回
hajimekata-nyumon-donyu.hatenablog.com
drken(けんちょん)さんの記事の進行で進みます。
こんにちは、今回も学んだことを共有させていただきます。
途中に他の方のコードを引用しますが、そのコードの著作権はそのコードを書いた方が有しています。
元のコードが見たい場合は今回は全部僕のコードです。問題名 提出#〇〇〇〇〇
で検索をすれば見ることができます。
※注 スマホだとコードは見にくいのでPCモードかPCで見たほうが良いです。
At Corder で見つけた解答から得たもの
・AtCoder Beginner Contest102_B
問)「長さ Nの整数列 Aが与えられます。 Aの(添字の)異なる 2要素の差の絶対値の最大値を求めてください。」 これは、A(1)~A(N)までの要素の中で差の絶対値を求めて最大値を出力せよという問題です。 取り敢えず、自分の解答とその解法の説明をば。
signed main() { int n; cin >> n; vi v1(0); int a; REP(i, n){ cin >> a; v1.PB(a); } int max = *max_element(ALL(v1)); int min = *min_element(ALL(v1)); cout << max - min; return 0; }
めっちゃシンプルなので分かりやすいと思います。 コンテナの中に与えられた要素を追加していって入力を完了。次に、max_elementとmin_elementでコンテナの中の最大値である要素と最小値である要素をピックアップします。 その二つの差をとれば必ず差が最も大きくなるという解法です。 (ALL(v1)というのは、(v1.begin(),v1.end())の#defineによるマクロです。僕のテンプレートの記事に詳しいことが載っています。)
hajimekata-nyumon-donyu.hatenablog.com
思いついたので別解
int main() { int n; cin >> n; int a[n]; for (int i = 0; i < n; i++) { cin >> a[i]; } sort(a, a + n); int max = a[n - 1]; int min = a[0]; cout << abs(max - min) << endl; }
これは、まず配列の中に入力をします。その後sortして最大値と最小値を取得し、差の絶対値の最大値を求めます。 アルゴリズム的には変わりませんが、最大値と最小値を得るプロセスが違います。
Tips01_sortしたら必ずa[o]が最小値
・当たり前ですけど、これを使った解法を見たことがなかったら多分, 解答に利用しようと思わないんじゃないかなと思い書きました。
・ABC113_B
問題は各自参照してください。 まず、割かしきれいだと思う自分の解答をば。
signed main() { int options; cin >> options; int t; cin >> t; int a; cin >> a; a *= 1000; int h; int ave; vi H(0); REP(i, options){ cin >> h; H.PB(h); } vi Ave(0); REP(i, options){ ave = t*1000-H[i]*6; Ave.PB(ave); } REP(i, options) if(Ave[i] == a){ cout << i+1; return 0; } REP(i, options){ Ave[i] = a - Ave[i]; } REP(i, options) if(Ave[i] < 0) Ave[i] *= -1; int min = *min_element(ALL(Ave)); REP(i, options) if(min == Ave[i]){ cout << i+1; return 0; } return 0; }
これは、まず平均気温をT-H(i)×0.006で求められるので、それをコンテナに格納します。 しかし、t×1000-H[i]×6のようにすることで浮動小数点型を用いずに整数型のみで計算することが可能です。 あとはif文で場合分けをしてやります。 平均気温と目標とする気温が等しければ勿論その時点で解答を出力。そうでなければ、それらの差の絶対値を求めて、差が最小値のものを求めます。
REP(i, options){ Ave[i] = a - Ave[i]; } REP(i, options) if(Ave[i] < 0) Ave[i] *= -1;
この操作は差の絶対値を求めています。 差の絶対値が最小である添え字の地点が最も目標の気温に近いのでその添え字を出力してやればよいです。
さいごに
今回はsortによって最大値と最小値を求められることや、max_element()関数などを利用して最大値、最小値に関わる問題を解くことを学べたのではないでしょうか。 以上!ABC 102とABC113の解説でしたー。
次回はここをクリック
AtCoderの良解答からみるプログラミングのテクニック03(ビギナー向け) C++
このシリーズ第一回(まだ見てないならここをクリックしてからのほうが良いです)
前回の記事
drken(けんちょん)さんの記事の進行で進みます。
こんにちは、今回も学んだことを共有させていただきます。
途中に他の方のコードを引用しますが、そのコードの著作権はそのコードを書いた方が有しています。
元のコードが見たい場合は
今回から第九回までは問題名 提出#〇〇〇〇〇
で検索をすれば見ることができます。提出#〇〇〇〇〇
のかたちで紹介させていただきます。が十回目からはリンクを張ることにしました。検索してもでず、地道に番号から探さないと見つけられないみたいだからです。
※注 スマホだとコードは見にくいのでPCモードかPCで見たほうが良いです。
At Corder で見つけた解答から得たもの
・ABC068_B
提出 #4392940より
//入力 int n; cin >> n; //処理 int ans = log2(n); //出力 cout << pow(2,ans) << ENT; return 0;
log使うって発想ですかね、ここから学んだのは。処理ブロックを見ると、logを使ってint型の変数ansを初期化しているのが分かります。そのためlog2(7)だったらlog2(4)と同値になるのでそれを利用して出力するという解法。覚えておきたいですね。 コメントで区分けしているのも面白いと思った。
提出 #4387441より
int n, i; std::cin >> n; for(i = 1;i <= n;i = i * 2){} std::cout << i / 2; return 0;
これは解説と同じロジックの解法ですね。ですが、もう一つ同じロジックの解法があって、友達のコードから
int main(){ int a; cin >> a; if(a >= 64){ cout << 64 << endl; } else if(a >= 32){ cout << 32 << endl; } else if(a >= 16){ cout << 16 << endl; } else if(a >= 8){ cout << 8 << endl; } else if(a >= 4){ cout << 4 << endl; } else if(a >= 2){ cout << 2 << endl; } else{ cout << 1 << endl; } }
という泥臭いといえば失礼かもしれませんがこのような解答もありました。しかし、これはテニスやったことがある人は顧問の先生にも言われたと思うのですが、粘れだとか泥臭くやれだとか、勝つため(今回なら問題を解くため)にできることを例え周りからかっこいい勝ち方だと思われなくてもやれなんて言われたことがある自分からするとハッとさせられる解答だなぁと思いました。 ださかっこいいというか。解くことに貪欲な姿勢がイカしてるというか。 そういえば、受験でも言われた気がする。確率は最後の手段で泥臭くやってもいい。受かるのが大事。みたいな。 ちなみに自分はこう解きました↓
signed main() { int n; cin >> n; vector<int> v1, copyv1; vector<int> count(n, 0); for (int i = 1; i < n+1; i++) { v1.push_back(i); } copyv1 = v1; for (int i = 0; i < n; i++) { int j = 0; while(true){ if(v1[i]%2 == 1) break; else { v1[i]/=2; ++j; } count[i] = j; } } auto maxitr = max_element(count.begin(), count.end()); int maxindex = distance(count.begin(), maxitr); cout << copyv1[maxindex] << endl; return 0; }
まぁ、なんでこうなったかというと、一番最初にAtCorderやるならこれを読めということで紹介した記事に「081_Bに似てます」って書いてたんで似た解き方をしようとした結果ですね。 で、多分最後に紹介するこれに似た解答を一番見かけた気がする。まぁ、実質二番目に紹介したのと変わらないけど。ちなみにこれは私が書いた別解です。
int main(){ int n, ans; cin >> n; for(int i=0; i<10; i++){ if(pow(2, i) > n){ ans = pow(2, i-1); break; } } cout << ans << endl; return 0; }
今回は自分の解答のコスパが悪い(コード書く時間が長いということ)ので、別解の方が参考になると思います。
おわり
以上!ABC 068の解説でしたー。
次回はここをクリック
AtCoderの良解答からみるプログラミングのテクニック02(ビギナー向け) C++
前回の記事はこちら
drken(けんちょん)さんの記事の進行で進みます。
こんにちは、今回も学んだことを共有させていただきます。
途中に他の方のコードを引用しますが、そのコードの著作権はそのコードを書いた方が有しています。
元のコードが見たい場合は
今回から第九回までは問題名 提出#〇〇〇〇〇
で検索をすれば見ることができます。提出#〇〇〇〇〇
のかたちで紹介させていただきます。が十回目からはリンクを張ることにしました。検索してもでず、地道に番号から探さないと見つけられないみたいだからです。
※注 スマホだとコードは見にくいのでPCモードかPCで見たほうが良いです。
At Corder で見つけた良解答から得たもの
ABC081_A・・・与えられた1と0のみで構成された整数の中に1は何回出現しましたか?
提出 #4349748を見ると
int main() { int a ; cin >> a ; cout << a%2 + a/100 + (a/10)%2 << endl; }
のように解答しています。これ面白いですよね。一応自分も除算をする選択肢は持っていたのですが、それよりもstringで処理したほうが良いと判断してstringで処理しました。ですのでこれを見た時よく除算でやりきったなぁと思いました。 それはさておき、解説に入りましょう。 これは、一の位がa%2、十の位が(a/10)%2そして、百の位がa/100と対応していると思います。そして、それぞれの計算結果は必ず1か0になります。この計算結果はもちろん、ある位は1なのか0なのかを表しています。最後に全ての位を足すことで1が整数の中に出現した回数を出力することができています。面白いですね。
Tips01_後置演算子は必要な時だけ使用する。
++iとi++のような形でよく使われるインクリメント演算子。これは後置よりも前置で使うことがC++では推奨されています。 なぜかというと後置では一度iをコピーしてからインクリメントされていない値を結果として返す必要があるからです。そのため、前置と比べて処理が増えるからです。もちろん必要な時は後置を使いましょう。
Tips02_配列の添え字を定義するときには、size_t型を使いましょう。
c言語だとfor(int i = 0; ・・・・)
のように書けますが(c99とかだとね)、c++だとfor(size_t i = 0; ・・・・)
が推奨されてます。
ABC081_B
これの僕の解答はこんな感じです。
#include <bits/stdc++.h> using namespace std; 略 signed main() { int a; cin >> a; int n; int cnt = 0; bool flag = false; vector<int> v1(0); while(cin >> n) v1.PB(n); while(!flag){ for(const auto& i: v1){ if(i%2 != 0) flag = true; } if(flag != true){ /* for(auto& i: v1) i /= 2; */ ++cnt; } } /* for(const auto& i: v1) cout << i; */ cout << cnt; return 0; }
C++ですよ!って感じしますよね。なんかfor文は極力範囲for文で書いて、配列じゃなくてvectorを使ってやるぜという意気込みを感じます。 はい、解説しますと全部が偶数であるかをwhile文中で調べて割り切れないときにflagをtrueにします。次に、全てが偶数の時にcnt(よくあるカウンタ変数)をインクリメントし、インクリメントした回数を出力するという仕組みです。
で、模範解答を見ると、与えられた整数それぞれが最大で何回 2 で割れるかを求めてその最小値を求めるというプログラムを書いているのを見ました。初めて見た時はああ、こんな解き方もあるのかと感銘を受けました。
おわり
以上!ABC 081のAとBの解説でしたー。
次回はここをクリック