はじめに
皆様どうも、ばるとーくです。
今回も振り返りやっていきましょうかねぇ。
今回は残念ながら2完でした…
いけそうだったんだけどなぁ~(後述しますがガチで惜しいミスしてます)

あとどうでもいいけどさ、
なんかチンチロの問題出るみたいなのXで聞いて、実際でたじゃん?
何でかすぐにはわからんかったんやけど、なるほど、456(シゴロ)やからかぁ~って途中で気づいた()
振り返り
A問題
問題文
6 つの面を持つサイコロが 3 個あります。
どのサイコロも、面には 1,2,3,4,5,6 が書かれています。これらのサイコロを同時に振ったとき、出た目の合計が X になることはありますか?
制約
- X は 1 以上 20 以下の整数
これは1発ACを取れました。(いや流石にね…?)
こんなコードです。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// --- 初期設定(入出力の高速化と小数15桁出力) ---
struct Init {
Init() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout << fixed << setprecision(15);
}
} init;
// ------------------------------------------------
int main() {
int x;
cin >> x;
for (int i = 1; i <= 6; i++) {
for (int j = 1; j <= 6; j++) {
for (int k = 1; k <= 6; k++) {
if(i + j + k == x){
cout << "Yes" << "\n";
return 0;
}
}
}
}
cout << "No" << "\n";
return 0;
}3重for文でゴリ押しさせていただきました~
スピード重視なら別の方法あるだろうけど、まあヨシ!
B問題
問題文
6 つの面を持つサイコロが 3 個あります。
i 個目のサイコロの j 個目の面には Ai,j が書かれています。
どのサイコロも、各面が出る確率は 61 です。これらのサイコロを同時に振ったとき、4,5,6 の書かれた目が 1 つずつ出る確率を求めてください。
制約
- Ai,j は 1 以上 6 以下の整数
これ、時間を30分くらい使った上になかなかWAから抜け出せずにいました。
時間をつかった原因は…数弱だからかな?
※自分で言ってて悲しいです
4の個数、5の個数、6の個数がそれぞれ違うときの処理をどうするか考えた結果、ごりごりに樹形図を書いて、それぞれの場合で確率計算を行い、最後に足すことにしました。
せいぜい6通りしかないので問題はないでしょう
ではWAが取れなかった原因は何かというと…⁉
タイポ、つまりタイプミスです…(悲しい)
ちょっと見比べてもらおうかと思います。
左側がWAのとき、右側がACのときのコードです。
何が違うかおわかりでしょうか?(違う部分にマーカー入れてます)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// --- 初期設定(入出力の高速化と小数15桁出力) ---
struct Init {
Init() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout << fixed << setprecision(20);
}
} init;
// ------------------------------------------------
int main() {
double a4 = 0, a5 = 0, a6 = 0, b4 = 0, b5 = 0,
b6 = 0, c4 = 0, c5 = 0, c6 = 0;
for (int i = 0; i < 6; i++) {
int inp;
cin >> inp;
if (inp == 4) {
++a4;
} else if (inp == 5) {
++a5;
} else if (inp == 6) {
++a6;
}
}
for (int i = 0; i < 6; i++) {
int inp;
cin >> inp;
if (inp == 4) {
++b4;
} else if (inp == 5) {
++b5;
} else if (inp == 6) {
++b6;
}
}
for (int i = 0; i < 6; i++) {
int inp;
cin >> inp;
if (inp == 4) {
++c4;
} else if (inp == 5) {
++c5;
} else if (inp == 6) {
++c6;
}
}
double a, b, c, d, e, f;
a = (a4 / 6) * (b5 / 6) * (c6 / 6);
b = (a4 / 6) * (b6 / 6) * (c5 / 6);
c = (a5 / 6) * (b4 / 6) * (c6 / 6);
d = (a5 / 6) * (b6 / 6) * (c6 / 6);
e = (a6 / 6) * (b4 / 6) * (c5 / 6);
f = (a6 / 6) * (b5 / 6) * (c4 / 6);
cout << a + b + c + d + e + f << "\n";
return 0;
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// --- 初期設定(入出力の高速化と小数15桁出力) ---
struct Init {
Init() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout << fixed << setprecision(20);
}
} init;
// ------------------------------------------------
int main() {
double a4 = 0, a5 = 0, a6 = 0, b4 = 0, b5 = 0,
b6 = 0, c4 = 0, c5 = 0, c6 = 0;
for (int i = 0; i < 6; i++) {
int inp;
cin >> inp;
if (inp == 4) {
++a4;
} else if (inp == 5) {
++a5;
} else if (inp == 6) {
++a6;
}
}
for (int i = 0; i < 6; i++) {
int inp;
cin >> inp;
if (inp == 4) {
++b4;
} else if (inp == 5) {
++b5;
} else if (inp == 6) {
++b6;
}
}
for (int i = 0; i < 6; i++) {
int inp;
cin >> inp;
if (inp == 4) {
++c4;
} else if (inp == 5) {
++c5;
} else if (inp == 6) {
++c6;
}
}
double a, b, c, d, e, f;
a = (a4 / 6) * (b5 / 6) * (c6 / 6);
b = (a4 / 6) * (b6 / 6) * (c5 / 6);
c = (a5 / 6) * (b4 / 6) * (c6 / 6);
d = (a5 / 6) * (b6 / 6) * (c4 / 6);
e = (a6 / 6) * (b4 / 6) * (c5 / 6);
f = (a6 / 6) * (b5 / 6) * (c4 / 6);
cout << a + b + c + d + e + f << "\n";
return 0;
}
そう、問題は下から7行目。このマーカー部分です。
ここに速く気づいていれば…
C問題
問題文
a,b,cからなる文字列 S が与えられます。S の空でない部分文字列であって、同じ文字が隣り合わないものの個数を 998244353 で割った余りを求めてください。
ただし、 2 つの部分文字列が文字列として一致しても、取り出す位置が異なるならば区別するものとします。
部分文字列とは
S の部分文字列とは、S の先頭から 0 文字以上、末尾から 0 文字以上削除して得られる文字列のことをいいます。 例えば、
abはabcの部分文字列ですが、acはabcの部分文字列ではありません。制約
- S は
a,b,cからなる長さ 1 以上 3×105 以下の文字列
この問題はフルフルに時間を使っても解けず、時間切れとなりました。
同じ文字が隣り合わないようにしたいなら、ってことで、文字列をランレングス圧縮してから数え上げることにしました。
これが最終的に書いたコードです。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// --- 初期設定(入出力の高速化と小数15桁出力) ---
struct Init {
Init() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout << fixed << setprecision(15);
}
} init;
// ------------------------------------------------
int main() {
string s;
cin >> s;
ll ans = 1;
vector<ll> rlen(0);
char f = 'z';
int len = 1;
for (int i = 0; i < s.size(); i++) {
if (i == 0) {
f = s[i];
len = 1;
} else {
if (s[i] != f) {
rlen.push_back(len);
f = s[i];
len = 1;
} else {
++len;
}
}
}
rlen.push_back(len);
cerr << rlen.size() << "\n";
for (ll i : rlen) {
cerr << i << endl;
}
int l = 0;
for (int i = 1; i < rlen.size(); i++) {
if (rlen[i] != 1) {
ans += rlen[i] + (i - l) * (i - l + 1) / 2;
l = i;
} else {
++ans;
}
cerr << ans << "\n";
ans %= 998244353;
}
ans += (rlen.size() - l - 1) * (rlen.size() - l)/ 2;
cout << ans % 998244353 << endl;
return 0;
}しかしWAでした。
全く理由がわからず、Claude君に聞きました。
そしたらこういわれました。
ll ans = 1;の初期化です。これは「最初のランから 1 文字だけ寄与する」と決め打ちしている形ですが、rlen[0] が 2 以上のときに (rlen[0] − 1) 個ぶん足りなくなります。具体例:
入力 コード出力 正解 差 "aaaa"1 4 3 "aabbcc"7 8 1 "aaabb"4 6 2 ずれは常に
rlen[0] − 1。rlen[0] == 1のテストケースだけ通ったので気づきにくかったやつです。
- 修正
rlen.push_back(len);の直後でcpp
ll ans = rlen[0];に直せばロジック自体は通ります。それ以降の
(i - l)(i - l + 1)/2の式と末尾項は、実は正しく組めていて、l ≤ a < b ≤ iを満たすクロスラン部分文字列をちゃんと数え上げてます。最初の1点だけが穴でした。
要するに、自分は「最初の文字は1つしか連続しないだろう」と。そう思い切っており、このパターンを見逃していたのが敗因です…
いや、マジで盲点でした…
※気づけてたら終了の20分前には通せてた
とはいえ、ちゃんと自分で気づけた点もありました。数え上げ方とか(最初階乗と勘違い)、総和の公式を使った計算量節約とか…
さいごに
反省点として、「コードレビューをもっとちゃんとする」ことを重点に置きたいですね…
WAで返ってきたときは、Easy testだけに頼らず自分でもテストケースを考えていく癖、要りますね…
※今までなかったんかよ⁉って話ではあるんですけども…極度の面倒くさがりなのが裏目にぃぃ
あとは、4Q~2Qの問題をもっとちゃんと詰めたいです。
いや、まさにやるべきなんだろうけども
最後に、レート推移を貼って終わりたいと思いまする

これからも、とりあえず茶色を目指してがんまります!
ちなみに今日のせいで400になるために973パフォが必要になってしまった。
360には702パフォが必要である。こっちならまだ現実的かもね。


コメント