AtCoder Regular Contest 037 C - 億マス計算

問題

 N個の数列が二つ与えられ、それぞれa_1, \dots, a_Nおよびb_1, \dots, b_Nとする。これらを用いてNN列の表を、ij列目にはa_i \times b_jが書き込まれるように作成する。書き込まれた数字の中でK番目に大きい数字を求めよ。

解法

 K番目に大きい数字とは「その数字以下の数字がK個以上書き込まれているようなもののなかで最小の数字」である。これを二分探索する。

 ある数字X以下の数字が何個書き込まれているかは、あらかじめa_1, \dots, a_Nb_1, \dots, b_Nをソートしておけば、iを固定したときa_ib_jをかけた値がX以下となるjを二分探索で求めることができる。

 よって全体でa_1, \dots, a_Nb_1, \dots, b_Nの中での最大値をそれぞれA, Bとしたとき、計算量はO(N(\log{N})(\log{AB}))となりこの問題が解けた。

反省

 24分12秒(0WA)でAC。最初はよくわからなかったけどまずa,bをソートするんだろうなというのは思いついたところ。そしてサンプルの3個目について表を作ってみたらなんとなく各a_iに対してどこまで小さい数字かは二分探索で求めることができそうだなとわかり、また最近『K番目に大きい数字とは「その数字以下の数字がK個以上書き込まれているようなもののなかで最小の数字」』みたいな変換をARC101のMedian of Mediansでやったのを思い出して想定解がわかった。典型と言えるような変換なんだろう。

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

int main() {
    ll N, K;
    cin >> N >> K;
    vector<ll> a(N), b(N);
    for (ll i = 0; i < N; i++) {
        cin >> a[i];
    }
    for (ll i = 0; i < N; i++) {
        cin >> b[i];
    }
    sort(a.begin(), a.end());
    sort(b.begin(), b.end());

    auto isOK = [&](ll x) {
        //x以下のものがK個以上あるか判定
        ll num = 0;
        for (ll i = 0; i < N; i++) {
            if (a[i] * b[0] > x) {
                //ここから先でx以下となることはない
                break;
            }
            if (a[i] * b[N - 1] <= x) {
                //全てx以下
                num += N;
                continue;
            }

            ll low = 0, high = N - 1;
            while (low + 1 != high) {
                ll mid = (low + high) / 2;
                (a[i] * b[mid] <= x ? low = mid : high = mid);
            }
            num += low + 1;
        }
        return num >= K;
    };

    ll ng = 0, ok = pow(1e9, 2) + 1;
    while (ng + 1 != ok) {
        ll mid = (ok + ng) / 2;
        (isOK(mid) ? ok = mid : ng = mid);
    }

    cout << ok << endl;
}

AtCoder Regular Contest 035 C - アットコーダー王国の交通事情

問題

 N個の都市がM本の道路で結ばれている。道路には長さがあり、各都市i,j間の最短距離の総和をSとする。初期状態からK本の新しい道路を建設していくとき、各道路が作られた後のSを求めよ。

解法

 新たな道路が都市X,Yを長さZで結ぶとき、各都市i,jについて最短距離を更新し得るのはi \to X\to Y \to jあるいはi \to Y \to X \to jのどちらか。つまり一般の都市aからbへのコストを保持しておけばこれはO(1)で求まる。i,jの選び方がそれぞれN通りあり、新たな道路はK本なので全体の計算量はO(KN^2)。初期状態において各都市間の最短距離を求めることについてもワーシャルフロイド法を用いればO(N^3)で計算できるので間に合う。

反省

 23分00秒(0WA)でAC。問題を理解できればとくに難しいところもなく。和を差分計算する必要があるかなと一瞬思ったが、計算量的には変わらないので面倒くさくて毎回O(N^2)かけて計算するままに。400^3が間に合うものなのかちょっと不安だったが495msで通った。つまり10^9でも簡単な処理ならギリギリ通ってしまう? このあたりの間に合うかどうかの感覚はよくわからないまま。

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

int main() {
    ll N, M;
    cin >> N >> M;
    
    struct Edge {
        ll to, cost;
    };
    vector<vector<Edge>> edges(N);
    for (ll i = 0; i < M; i++) {
        ll A, B, C;
        cin >> A >> B >> C;
        A--; B--;
        edges[A].push_back({ B, C });
        edges[B].push_back({ A, C });
    }

    ll K;
    cin >> K;
    vector<ll> X(K), Y(K), Z(K);
    for (ll i = 0; i < K; i++) {
        cin >> X[i] >> Y[i] >> Z[i];
        X[i]--; Y[i]--;
    }

    vector<vector<ll>> cost(N, vector<ll>(N, INT_MAX));
    for (ll i = 0; i < N; i++) {
        for (auto e : edges[i]) {
            cost[i][e.to] = min(cost[i][e.to], e.cost);
        }
        cost[i][i] = 0;
    }
    for (ll k = 0; k < N; k++) {
        for (ll i = 0; i < N; i++) {
            for (ll j = 0; j < N; j++) {
                cost[i][j] = min(cost[i][j], cost[i][k] + cost[k][j]);
            }
        }
    }

    auto getSum = [&]() {
        ll sum = 0;
        for (ll i = 0; i < N; i++) {
            for (ll j = i + 1; j < N; j++) {
                sum += cost[i][j];
            }
        }
        return sum;
    };

    for (ll i = 0; i < K; i++) {
        for (ll j = 0; j < N; j++) {
            for (ll k = 0; k < N; k++) {
                ll cost1 = cost[j][X[i]] + cost[Y[i]][k] + Z[i];
                ll cost2 = cost[j][Y[i]] + cost[X[i]][k] + Z[i];
                cost[j][k] = min({ cost[j][k], cost1, cost2 });
                cost[k][j] = min({ cost[k][j], cost1, cost2 });
            }
        }
        cost[X[i]][Y[i]] = min(cost[X[i]][Y[i]], Z[i]);
        cost[Y[i]][X[i]] = min(cost[Y[i]][X[i]], Z[i]);

        cout << getSum() << endl;
    }
}

AtCoder Regular Contest 034 C - 約数かつ倍数

問題

 正整数A, Bが与えられる。A!の約数であり、かつB!の倍数でもあるような整数の個数を求めよ。

解法

 求めるものは結局\frac{A!}{B!}の約数の個数である。これはB+1, \dots, Aまでの数字を素因数分解し、登場する素因数の数を数えていくことで求めることができる(約数の個数の公式)。各素因数分解にかかる計算量は数字nに対してO(\sqrt{n})であり、A - B \le 100という制約があるためこれでこの問題を解くことができた。

反省

 56分42秒(1WA)で久々に自力AC。まずこの手の問題を見たら初手としてやることは小さい数字での実験と決めているんだけど、この問題ではあまり有効ではなかったかもしれない。いろいろ見ているうちに、ようやくABの差分のところだけを考えればいいことに気が付く。しかし約数の個数を上手く求める方法を忘れていてその後もかなり手間取ってしまった。しばらくしてから素因数分解すれば求まるんだったなと思ってググって確証を得てから実装し、なんとかAC。一度素因数分解をするところをO(n)で実装してしまいTLEを出してしまった。そこまで難しい問題でもないと思うのでもっとすんなり解きたかった。

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

constexpr ll MOD = (ll)1e9 + 7;

vector<pair<ll, ll>> getFactor(ll x) {
    vector<pair<ll, ll>> result;
    for (ll i = 2; i * i <= x; i++) {
        ll num = 0;
        while (x % i == 0) {
            x /= i;
            num++;
        }
        if (num) {
            result.push_back({ i, num });
        }
        if (x <= 1) {
            break;
        }
    }
    if (x > 1) {
        result.push_back({ x, 1 });
    }
    return result;
}

ll solve(ll a, ll b) {
    map<ll, ll> factor;
    for (ll i = b + 1; i <= a; i++) {
        auto f = getFactor(i);
        for (auto p : f) {
            factor[p.first] += p.second;
        }
    }

    ll ans = 1;
    for (auto p : factor) {
        ans *= p.second + 1;
        ans %= MOD;
    }
    return ans;
}

int main() {
    ll A, B;
    cin >> A >> B;
    cout << solve(A, B) << endl;
}

SRM513 Div1 Easy - YetAnotherIncredibleMachine

問題

 n枚の板について長さと設置できる上下限位置が与えられる。またm個のボールが各座標に落とされる。板に一つもボールが落ちないような板の配置方法が何通りあるか求めよ。

解法

 ボールが落ちる位置について累積和を取って、各板が設置可能な範囲を二分探索で求める。

反省

 問題の把握にいくらか時間がかかったのと、端の関係とか二分探索の含める含めない、範囲指定などでかなり混乱した。

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

class  YetAnotherIncredibleMachine {
public:
    int countWays(vector<int> platformMount, vector<int> platformLength, vector<int> balls) {
        constexpr int MAX = 2e5;
        vector<int> num(2 * MAX + 2, 0);
       
        int* p = &num[MAX + 1];
        for (int b : balls) {
            p[b]++;
        }
        for (int i = -MAX; i < MAX; i++) {
            p[i + 1] += p[i];
        }

        constexpr ll MOD = 1e9 + 9;
        ll ans = 1;
        for (int i = 0; i < platformMount.size(); i++) {
            ll curr_pattern = 0;

            //可能な左端についてループする
            for (int l = platformMount[i] - platformLength[i]; l <= platformMount[i]; ) {
                if (p[l] == p[l - 1] + 1) {
                    l++;
                    continue;
                }
                int n = p[l];
                int r = upper_bound(&p[l], &p[platformMount[i] + platformLength[i] + 1], n) - &p[0];
                curr_pattern += max(r - l - platformLength[i], 0);
                curr_pattern %= MOD;
                l = r;
            }

            ans *= curr_pattern;
            ans %= MOD;
        }

        return ans;
    }
};

SRM512 Div1 Easy - MysteriousRestaurant

問題

 N日間営業するレストランで夕食を食べることを考える。レストランは常にM種類のパターンを用意しているが、ある曜日にあるパターンの夕食を食べると、同じ曜日には同じパターンの夕食した食べられない。i日目のj番目のパターンに対するコストを与えるので、budgetコスト内で1日目から連続して最大何日食べることができるかを求めよ。

解法

 i日目まで食べられるとして、j曜日に食べるべきパターンはそれぞれ独立に計算することができる。その計算にはO(NM)かかり全体としてO(N^2M\times7)となるが、制約からせいぜい7\times 50^3にしかならないのでこれでこの問題が解けた。

反省

 いろいろガチャガチャいじっていたらなんか解けた。あまり理屈っぽく解いている気がしない。

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

class MysteriousRestaurant {
public:
    int maxDays(vector<string> prices, int budget) {
        for (ll i = prices.size(); i >= 0; i--) {
            ll cost = 0;
            for (ll j = 0; j < min((ll)7, i); j++) {
                ll j_cost = INT_MAX;
                for (ll k = 0; k < prices[0].size(); k++) {
                    ll c = 0;
                    for (ll l = j; l < i; l += 7) {
                        c += char2value(prices[l][k]);
                    }
                    j_cost = min(j_cost, c);
                }
                cost += j_cost;
            }
            if (cost <= budget) {
                return (int)i;
            }
        }
        return 0;
    }
private:
    ll char2value(char c) {
        if ('0' <= c && c <= '9') {
            return c - '0';
        } else if ('A' <= c && c <= 'Z') {
            return c - 'A' + 10;
        } else {
            return c - 'a' + 36;
        }
    }
};

AtCoder Regular Contest 033 C - データ構造

問題

 数の集合Sに対する以下のクエリを処理せよ。

  • タイプ1 : Sに数Xを追加する。
  • タイプ2 : Sに含まれる数のうちX番目に小さい数を答え、その数をSから削除する。

解法

 「X番目に小さい数⇔それ以下の数がX個以上追加されている数のうち最も小さい数」と考え、ある数以下の数がS内にいくつあるかを高速に取得できれば二分探索で答えるべき数を求めることができる。これはセグメント木を使うことにより実装が可能であり、タイプ1である数が登場した際の操作、タイプ2において累積和を求める操作を各O(\log{N})で行えるためこの問題が解けた。

反省

 ここ数日、あまり机に向かう時間は取れなかったが移動中などにこの問題を考えていて、計数時間は考えたと思うが解けなかったので解説スライドを見た。セグメント木でやっていく発想が一切出てこなかったのは猛省するべき案件で、データ構造という問題名があまりにも露骨だったのでそういう解法を捨ててしまっていた。

 セグメント木の実装は今まで何回かやってきたが、いつもセグメント木をソラで書きたいあなたにを写してばっかりで何も学びを得ていなかったため、今回はかなり自分で考えながら実装してみた。といっても見た覚えがあるのでどうしても似た書き方になってしまうが……。

 数十分かけてなんとか実装できたと思ったが、被覆関係の処理が間違っていてO(\log{N})になっておらずTLE。仕方がなく先のページを見てそこだけ参考にした。

 今までセグメント木を実装できなかったのでセグメント木を使いそうな問題に対してもそうではない解法を考えてしまっていたが、さすがに逃げ続けるわけにはいかないのでちゃんと選択肢に入れて考察していきたい。

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

class SegmentTree {
public:
    SegmentTree(ll n) {
        ll radicand = (ll)ceil(log2(n));
        size_ = (ll)pow(2, radicand);
        nodes_.resize(2 * size_ - 1, 0);
    }
    void add(ll x, ll v) {
        for (ll i = x + size_ - 1; ; i = (i - 1) / 2) {
            nodes_[i] += v;
            if (i == 0) {
                break;
            }
        }
    }
    ll getSum(ll a, ll b, ll k = 0, ll l = 0, ll r = -1) {
        //[a, b)の和を求める
        if (r == -1) {
            r = size_;
        }
        assert(a < b);
        assert(l < r);
        //printf("(%lld, %lld, %lld, %lld, %lld)\n", a, b, k, l, r);

        //nodes_[k]が担当する範囲が[l, r)
        if (b <= l || r <= a) {
            //被覆なし
            return 0;
        }

        if (a <= l && r <= b) {
            //完全被覆
            return nodes_[k];
        }

        //部分被覆
        ll kl = (k + 1) * 2 - 1;
        ll kr = (k + 1) * 2;
        return getSum(a, b, kl, l, (l + r) / 2) + getSum(a, b, kr, (l + r) / 2, r);
    }
private:
    ll size_;
    vector<ll> nodes_;
};

int main() {
    ll Q;
    cin >> Q;

    vector<ll> T(Q), X(Q);
    for (ll i = 0; i < Q; i++) {
        cin >> T[i] >> X[i];
    }

    constexpr ll MAX = (ll)2e5;
    SegmentTree st(MAX + 1);
    for (ll i = 0; i < Q; i++) {
        if (T[i] == 1) {
            st.add(X[i], 1);
        } else {
            ll low = 0, high = MAX + 1;
            while (low + 1 != high) {
                ll mid = (low + high) / 2;
                (st.getSum(0, mid) >= X[i] ? high = mid : low = mid);
            }
            cout << low << endl;
            st.add(low, -1);
        }
    }
}

AtCoder Regular Contest 032 C - 仕事計画

問題

 N個の仕事があり、i番目の仕事は時刻a_iに始まりb_iに終わる。時刻が重ならないようにできるだけ多くの仕事をこなすことを考えたとき、こなす仕事の種類を選び方が複数ある場合は辞書順最小のものとして構築せよ。

解法

 dp[i] := 時刻iから終了までにこなせる仕事の数及び最初にこなすべき仕事のインデックスとして後ろから求めていく。遷移は仕事を行わないときdp[i] = dp[i + 1]であり、仕事をこなすときは各仕事に対してそれを行ったときより多くの仕事がこなせるなら必ずそれを選び、同数になるなら辞書順最小のものを選ぶ、としていく。

反省

 昨日40分ほど考えて解けず、今日もまた40分ほど考えて解けず解説スライドを見て通した。

 想定解と似たような感じで各時刻iについてdp[i]を考えるというところには思い至っていたが、stringで経路を全て持とうとしてメモリが足りないことに(おそらく)なっていたり、前からdpしていくことしか考えていなかったので辞書順を上手く最小化するのがどうしても上手くいかなかった。

 forループDPを考えるときに順番を上手くやるのが苦手で、メモ化再帰で解きに行った方が可能性があったかもしれない。辞書順最小ということを考えなければ典型DPであり、なぜか典型はforループでやりたがってしまうという性質があるためそこに固執してしまった。

 解説を見てからも時刻iから始まる仕事を行わないときの遷移が間違っていて2WAほど出してしまった。どうも遷移を考える丁寧さも足りず、全体的にDPは苦手という意識が強い。

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

constexpr ll MAX = (ll)1e6;

int main() {
    ll N;
    cin >> N;
    struct Job {
        ll start, end;
    };
    vector<Job> jobs(N);
    for (ll i = 0; i < N; i++) {
        cin >> jobs[i].start >> jobs[i].end;
    }

    struct Edge {
        ll to, index;
    };
    vector<vector<Edge>> edges(MAX);
    for (ll i = 0; i < N; i++) {
        edges[jobs[i].start].push_back({ jobs[i].end, i });
    }

    struct Element {
        ll cost, next;
    };
    vector<Element> dp(MAX + 1, { 0, INT_MAX });

    for (ll i = MAX - 1; i >= 0; i--) {
        //時刻iから始まる仕事を行わない
        dp[i] = dp[i + 1];

        //時刻iから始まる仕事を行う
        for (Edge j : edges[i]) {
            ll new_cost = dp[j.to].cost + 1;
            if (dp[i].cost < new_cost) {
                dp[i].cost = new_cost;
                dp[i].next = j.index;
            } else if (dp[i].cost == new_cost) {
                dp[i].next = min(dp[i].next, j.index);
            }
        }
    }

    ll ans = dp[0].cost;
    cout << ans << endl;

    ll curr_time = 0;
    for (ll i = 0; i < ans; i++) {
        cout << dp[curr_time].next + 1 << " \n"[i == ans - 1];
        curr_time = jobs[dp[curr_time].next].end;
    }
}

AtCoder Beginner Contest 109 参加ログ

結果

 10分ちょっと遅れてスタートし、24分ほどかけて全完。Dでアホみたいな2WA出してペナルティ食らったのは反省。

A - ABC333

 脳みそを使いたくなかったので愚直にやった。コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

int main() {
    ll A, B;
    cin >> A >> B;
    for (ll C = 1; C <= 3; C++) {
        if (A * B * C % 2 == 1) {
            cout << "Yes" << endl;
            return 0;
        }
    }
    cout << "No" << endl;
}

B - Shiritori

 std::mapで2回以上出るかを数えて、あとは前のものの最後と今回の先頭文字が一致しているかを確認。コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

int main() {
    ll N;
    cin >> N;
    vector<string> W(N);
    for (ll i = 0; i < N; i++) {
        cin >> W[i];
    }

    map<string, ll> mp;
    for (ll i = 0; i < N; i++) {
        if (++mp[W[i]] == 2) {
            cout << "No" << endl;
            return 0;
        }
        if (i > 0 && W[i - 1].back() != W[i].front()) {
            cout << "No" << endl;
            return 0;
        }
    }

    cout << "Yes" << endl;
}

C - Skip

 x_1, \dots, x_NXを一つの配列に詰めてソートして、階差の最大公約数を取っていったものが答え。厳密には証明してないけど、まぁこれでしょという感じで解いた。コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

ll gcd(ll a, ll b) {
    return (b ? gcd(b, a % b) : a);
}

int main() {
    ll N, X;
    cin >> N >> X;
    vector<ll> x(N);
    for (ll i = 0; i < N; i++) {
        cin >> x[i];
    }
    x.push_back(X);
    sort(x.begin(), x.end());

    ll ans;
    for (ll i = 0; i < N; i++) {
        ll diff = x[i + 1] - x[i];
        if (i == 0) {
            ans = diff;
        } else {
            ans = gcd(ans, diff);
        }
    }

    cout << ans << endl;
}

D - Make Them Even

 各列について左から右へ見ていき、奇数だったら右に一個流す。最後に各行の一番右を見ていき、奇数だったら下に一個流す。間違えてa[i][j] % 2 == 0とか書いていたのと、最初に操作回数を表示することを忘れていて2回WA。コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

int main() {
    ll H, W;
    cin >> H >> W;

    vector<vector<ll>> a(H, vector<ll>(W));
    for (ll i = 0; i < H; i++) {
        for (ll j = 0; j < W; j++) {
            cin >> a[i][j];
        }
    }

    vector<ll> y, x, yy, xx;
    for (ll i = 0; i < H; i++) {
        for (ll j = 0; j < W - 1; j++) {
            if (a[i][j] % 2 == 1) {
                //右へ
                y.push_back(i + 1);
                x.push_back(j + 1);
                yy.push_back(i + 1);
                xx.push_back(j + 2);
                a[i][j]--;
                a[i][j + 1]++;
            }
        }
    }
    for (ll i = 0; i < H - 1; i++) {
        if (a[i][W - 1] % 2 == 1) {
            //下へ
            y.push_back(i + 1);
            x.push_back(W);
            yy.push_back(i + 2);
            xx.push_back(W);
            a[i][W - 1]--;
            a[i + 1][W - 1]++;
        }
    }

    cout << y.size() << endl;
    for (ll i = 0; i < y.size(); i++) {
        cout << y[i] << " " << x[i] << " " << yy[i] << " " << xx[i] << endl;
    }
}

AtCoder Regular Contest 031 C - 積み木

問題

 全て高さが違うN個の積み木が1列に並べられている。隣り合う積み木を交換する操作ができるとき、一番高い積み木から順に左右へ低くなっていくような状態へ変化させるのに必要な最小の操作回数を求めよ。

解法

 一番大きな積み木から位置を確定させていくとして、ある積み木を確定させるときには今の位置から左側、右側について自分より大きな積み木がある個数がわかれば、小さい方へ交換していけばいい。これはBinary Indexed Treeを用いることでO(\log{N})で求めることができるので、全体O(N\log{N})でこの問題が解けた。

反省

 1時間43分(0WA)でAC。最初の30分くらいは眠くて何も考えられなかった。そこからバブルソートの交換数みたいな感じで、自分より大きいものの数が重要になってくるんだろうというところまでは考察できた。一番高い積み木の位置を決めて、左右についてまず求めて、一番高い積み木の位置をずらしたときの差分を計算していくことでO(N\log{N})とかにならないかなと考えていたものの、わからなかったので1時間15分くらい経ったところで解説スライドを見た。

 BinaryIndexedTreeを知らなかったのでこのページとかを調べて実装する。1-indexというのが厄介でちょっと手間取ったりした。(i & -i)で最右ビットが求めるのすごい。

 小さい方から確定させていくというのがよくわからなくて、結局大きい方から確定させていく方針で通した。小さい方からやっていく場合BITの値を直接いじらないといけないのかな?

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

class BinaryIndexedTree {
public:
    //1-indexed
    //(i & -i)はその番号が管理する区間の長さを表す
    //参考:http://hos.ac/slides/20140319_bit.pdf
    BinaryIndexedTree(ll n) : bit_(n + 1, 0) {}
    void add(ll a, ll w) {
        for (ll i = a; i < bit_.size(); i += (i & -i)) {
            bit_[i] += w;
        }
    }
    ll sum(ll a) {
        ll result = 0;
        for (ll i = a; i > 0; i -= (i & -i)) {
            result += bit_[i];
        }
        return result; 
    }
private:
    vector<ll> bit_;
};

int main() {
    ll N;
    cin >> N;
    vector<ll> B(N);
    vector<ll> index(N + 1);
    for (ll i = 0; i < N; i++) {
        cin >> B[i];
        index[B[i]] = i + 1;
    }

    BinaryIndexedTree bit(N);

    ll ans = 0;
    for (ll i = N; i >= 1; i--) {
        ll left = bit.sum(index[i]);
        ll right = N - i - left;
        ans += min(left, right);
        bit.add(index[i], 1);
    }

    cout << ans << endl;
}

SRM511 Div1 Easy - Zoo

解法

 一番身長が高いもの以外、ある数字を言う動物は1匹か2匹。0から最大値までそれ以外が出てきたらおかしく、また一度1になってから再び2になるのもおかしいのでそれも弾く。それ以外2匹のときはどっちかを選べるので×2,最後が1だったらさらに×2をすれば答えが求まる。

コード

#include"bits/stdc++.h"
using namespace std;
using ll = int64_t;

class Zoo {
public:
    long long theCount(vector<int> answers) {
        sort(answers.begin(), answers.end());
        vector<ll> num(answers.back() + 1, 0);
        for (int a : answers) {
            num[a]++;
        }
        ll ans = 1;
        for (int i = 0; i <= answers.back(); i++) {
            if (num[i] > 2 || num[i] <= 0) {
                //数がおかしい
                return 0;
            }
            if (i > 0 && num[i] > num[i - 1]) {
                //増えるわけはない
                return 0;
            }
            ans *= num[i];
        }
        if (num[answers.back()] == 1) {
            ans *= 2;
        }
        return ans;
    }
};