スコルの知恵袋

主にプログラミング関係の気になったこと等をまとめたブログ

Codeforces Round #653 (Div. 3) 感想

Dashboard - Codeforces Round #653 (Div. 3) - Codeforces

感想

5完。

今回はA,B,Dが割り算関連の問題だった。こどふぉ、割り算とか約数・倍数が好きすぎやろ。

unratedなのでカフェインを入れずに参加したら、眠すぎてバグを出しまくった。

ほんとうはratedでもカフェインを入れずに参加したいけど、ダメそうだ。

A - Required Remainder

 p を整数として  k = xp + y と置ける。よって、  xp + y \le n を満たす最大の  p を求めればよくて、それは  p = \left\lfloor \frac{n - y}{x} \right\rfloor

提出コード

void solve() {
    int Q;
    cin >> Q;
    while (Q--) {
        ll X, Y, N;
        cin >> X >> Y >> N;
        ll p = (N - Y) / X;
        cout << p * X + Y << "\n";
    }
}

B - Multiply by 2, divide by 6

 n が2と3以外に素因数を持っていたらダメ。2と3以外に素因数を持たないとき  n = 2^p \cdot 3^q とおける。 できる操作は2を掛けるか6で割るかの2つで、前者をすると2の指数が1増えて、 後者をすると2と3の指数がそれぞれ1下がる。 なので、 p > q だとアウトで、 p \le q のとき答えは  q + (q - p) となる。

提出コード

void solve() {
    int Q;
    cin >> Q;
    while (Q--) {
        ll N;
        cin >> N;
        ll q2 = 0, q3 = 0;
        while (N % 2 == 0) {
            ++q2;
            N /= 2;
        }
        while (N % 3 == 0) {
            ++q3;
            N /= 3;
        }
        bool ok = true;
        if (N > 1) {
            ok = false;
        }
        if (q2 > q3) {
            ok = false;
        }
 
        if (ok) {
            cout << q3 + (q3 - q2) << "\n";
        }
        else {
            cout << -1 << "\n";
        }
    }
}

C - Move Brackets

「まず、 s の各文字を()の数を数えながら左から右に見ていったときに、 (の数より)の数の方が大きくなる回数  a を求める。 次に、今度は逆に右から左に見ていったときに、)の数より(の数の方が大きくなる回数  b を求める。 そして、 \min(a, b) を出力する。」

というエスパーを無証明で投げるとなんか通った(懺悔)

提出コード

void solve() {
    int Q;
    cin >> Q;
    while (Q--) {
        ll N;
        string S;
        cin >> N >> S;
        ll ans = L_INF;
        ll cur = 0;
        ll cnt = 0;
        for (ll i = 0; i < N; i++) {
            if (S[i] == '(') {
                ++cur;
            }
            else {
                --cur;
            }
            if (cur < 0) {
                ++cnt;
                cur = 0;
            }
        }
 
        ans = cnt;
 
        cur = 0;
        cnt = 0;
        for (ll i = N - 1; i >= 0; i--) {
            if (S[i] == ')') {
                ++cur;
            }
            else {
                --cur;
            }
            if (cur < 0) {
                ++cnt;
                cur = 0;
            }
        }
 
        ans = min(ans, cur);
 
        cout << ans << "\n";
    }
}

コンテスト後によく考えると、方針はあってたけど  a b は必ず同じ数になることが分かった。

 s を左から右に見て、貪欲に()のペア(ただし、()より左になければならない)を作り、 ペアを作れた括弧を「良い括弧」、作れなかった括弧を「悪い括弧」と呼ぶことにする。

上の  a は悪い)の数と同義である。

また、操作の目的は悪い)(または()の個数を0にすることである。

で、どのように操作しても新たにできるペアの個数は高々1つで、かつ、 悪い括弧に対して操作を行うと必ず1つ新たにペアができるから、答えは  a に等しい。

貪欲にペアを作る方向を逆にすると同様にして答えが  b であることも示せるから  a = b

コンテスト後提出コード

void solve() {
    int Q;
    cin >> Q;
    while (Q--) {
        ll N;
        string S;
        cin >> N >> S;
        ll cur = 0;
        ll cnt = 0;
        for (ll i = 0; i < N; i++) {
            if (S[i] == '(') {
                ++cur;
            }
            else {
                --cur;
            }
            if (cur < 0) {
                ++cnt;
                cur = 0;
            }
        }
        cout << cnt << "\n";
    }
}

D - Zero Remainder Array

 x a_i \mod k で置き換える。そして、最初から  a_i \equiv 0 のものは以下では無視する。

 x は各操作ごとに0~k-1をぐるぐる回っていく。 b_i = k - a_i とおいて、各  a_i にはそれぞれ  b_i を当てればいいことになるけど、  b_i の値が同じものがあるとき、それらを処理するためには  x はその重複した個数の分だけ0~k-1をぐるぐる回らなければならない。

 f(c) を「 c = b_i を満たす  i の個数」として、 f(c) を最大化する  c の中で最も大きいものを  d としたとき、 答えは  k  (f(d) - 1) + d + 1 となる。

 f(c) を管理するのにunordered_mapを使ったらTLEしてびっくり。

mapに変えると無事にACした。unordered_mapの最悪計算量は  O(n) !!!!!

提出コード

void solve() {
    int Q;
    cin >> Q;
    map<ll, ll> ns;
    while (Q--) {
        ll N, K;
        cin >> N >> K;
        ns.clear();
        for (ll i = 0; i < N; i++) {
            ll a;
            cin >> a;
            if (a % K != 0) {
                ++ns[K - a % K];
            }
        }
 
        ll idx_mx = -1, mx = -1;
        for (const auto& [a, v] : ns) {
            if (v > mx) {
                idx_mx = a;
                mx = v;
            }
            else if (v == mx && a > idx_mx) {
                idx_mx = a;
            }
        }
 
        ll ans;
        if (mx == -1) {
            ans = 0;
        }
        else {
            ans = (mx - 1) * K + idx_mx + 1;
        }
 
        cout << ans << "\n";
    }
}

E1 - Reading Books

「AliceもBobも好きな本を読む数」さえ固定してしまえば、あとは全て貪欲に決められる。

提出コード

void solve() {
    ll N, K;
    cin >> N >> K;
    Vl abs, as, bs;
    for (ll i = 0; i < N; i++) {
        ll t, a, b;
        cin >> t >> a >> b;
        if (a == 1 && b == 1) {
            abs.emplace_back(t);
        }
        else if (a == 1) {
            as.emplace_back(t);
        }
        else if (b == 1) {
            bs.emplace_back(t);
        }
    }
    sort(abs.begin(), abs.end());
    sort(as.begin(), as.end());
    sort(bs.begin(), bs.end());
 
    ll nab = (ll)abs.size();
    ll na = (ll)as.size();
    ll nb = (ll)bs.size();
 
    Vl cumab(nab + 1), cuma(na + 1), cumb(nb + 1);
    for (ll i = 0; i < nab; i++) {
        cumab[i + 1] = cumab[i] + abs[i];
    }
    for (ll i = 0; i < na; i++) {
        cuma[i + 1] = cuma[i] + as[i];
    }
    for (ll i = 0; i < nb; i++) {
        cumb[i + 1] = cumb[i] + bs[i];
    }
 
    ll ans = L_INF;
    for (ll i = nab; i >= 0; i--) {
        ll x = max(K - i, 0ll);
        if (x > na || x > nb) {
            break;
        }
        ll t = cumab[i] + cuma[x] + cumb[x];
        ans = min(ans, t);
    }
 
    if (ans != L_INF)
        cout << ans << "\n";
    else
        cout << -1 << "\n";
}

E2 - Reading Books (hard version)

解けなかったし、まだACしていない。これも「AliceもBobも好きな本を読む数」を固定すれば解けそうな雰囲気があるけどどうだろ(わからん)。

F- Cyclic Shifts Sorting

読んでない。