一昨日はじめてAtcoderに参加しました。参加したのは[ AtCoder Beginner Contest 206 ]です。結果はA、Bのみ正解できました。
C問題は本番で解けませんでしたが、大会終了後頑張って理解しました。今回はその内容を共有していきたいと思います。
なお、実装は公式解説の1を参考にしました。この解説は公式解説の1を読んだ前提で進めていきます。
公式解説:https://atcoder.jp/contests/abc206/editorial/2096
全体のコード
解説するコードは以下の通り。
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
int main()
{
long long N;
cin >> N;
vector<long long> A(N);
for (int i = 0; i < N; ++i)
cin >> A[i];
long long count = ((N * (N - 1)) / 2);
sort(A.begin(), A.end());
map<int, long long> p_q;
for (int i = 0; i < N - 1; ++i)
{
if (A[i] != A[i + 1])
continue;
else if (!p_q[A[i]]) // if empty, then init by 1.
p_q[A[i]] = 1;
p_q[A[i]]++;
}
for (pair<int, long long> element : p_q)
{
long long q = element.second;
count -= ((q * (q - 1)) / 2);
}
cout << count << endl;
}
解説
以下の4つのステップで実装しました。題名が抽象的でスイマセン。
Step1:組み合わせをすべて数える
Step2:配列をソートする
Step3:連続する数と、連続した回数を記録する
Step4:Step3のデータをもとに答えから引く
解説には N=10, A = [ 7 8 1 1 8 9 9 6 8 2 ] というデータを具体例として使います。
【Step1】組み合わせをすべて数える
上の図のようなイメージで考えるとわかりやすいと思います。この場合は7をiとして残りをjとしています。組み合わせの数(矢印の数)は9個ですね。
次に8をiとしてそれ以降の数をjとして考えると8矢印の数は8個になります。
これを繰り返していくと組み合わせの総数は9+8+7+….+1となります。これは交差1,初項9の等差数列です。なのでその和は項数をNとすると(N * (N – 1)) / 2で与えられます。
long long count = ((N * (N - 1)) / 2);
【Step2】配列をソートする
配列を小さいものから大きいものの順番にソートします。これはalgorithmをincludeしてsortを使えば実装できます。
sort(A.begin(), A.end());
【Step3】連続する数と、連続した回数を記録する
Aをソートすると以下のような配列が与えられます。
1が2回、8が3回、9が2回連続しています。
以下のコードによって、このような「連続する数と、その回数のペア」を求めることができます。
map<int, long long> p_q;
for (int i = 0; i < N - 1; ++i)
{
if (A[i] != A[i + 1])
continue;
else if (!p_q[A[i]])
p_q[A[i]] = 1;
p_q[A[i]]++;
}
if文:i番目の数と、i+1番目の数が同じかどうか調べて、違かったらそれ以降の操作をする必要ないのでcontinueします。
else if文:この部分でははじめてp_qに値を登録するときに、1で初期化する処理をしています。
最後のとこ:i番目とi+1番目が等しく、かつすでに値がp_qに登録されている場合は単純に1を足します。
【Step4】Step3のデータをもとに答えから引く
まず、もう一度Step1の操作からどんな値を引きたいのか考えてみます。
例えば最初の8をiとして残りをjにして考えると、数えたいのは青色の矢印、数えたくないのは赤色の矢印になります。
2つ目の8をiとして残りをjとして数えると以下のようになります。
赤色の矢印は合計で2+1=3となります。
イメージが湧いたところで、赤色の矢印を数える方法を一般化してみましょう。
例えば7777という文字列の場合を考えます。最初の7を基準にして3本、2番めの7を基準にして2本、3番目の7を基準にして1本なので合計3+2+1です。
55555の場合は同様に4+3+2+1本の赤色の矢印があることになります。
結局赤色の矢印の合計は初項1,交差1、項数q-1の等差数列の和、すなわちq(q-1)/2になります。このqはStep3で求めた値です。1の場合はqが2、8は3、9は2です。
最後にStep1で求めた総数からそれぞれの赤い矢印を引いていきます。
count – (1の赤い矢印の合計) – (8の赤い矢印の合計) – (9の赤い矢印の合計)
↑のような感じで総数から引いていきます。
for (pair<int, long long> element : p_q)
{
long long q = element.second;
count -= ((q * (q - 1)) / 2);
}
おわりに(感想)
解説記事を書いてみたら想像以上に大変だった。