【C++で実装】Swappable [ AtCoder Beginner Contest 206 ] 【初心者による初心者のための解説】

一昨日はじめて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);
    }

おわりに(感想)

解説記事を書いてみたら想像以上に大変だった。

Copied title and URL