Educational Codeforces Round 181 (Div. 2) の A〜D 問題の解法

問題リンク: https://codeforces.com/contest/2125

A. Difficult Contest

解法

「NTT」と「FFT」はどちらも部分列として T が後ろに来る必要がある。したがって、まず F と N 以外の文字をすべて出力し、その後で F と N を出力すればよい。

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        string s;
        cin >> s;

        string ans;
        for (char c : s) {
            if (c != 'F' && c != 'N') ans += c;
        }
        for (char c : s) {
            if (c == 'F' || c == 'N') ans += c;
        }
        cout << ans << '\n';
    }
    return 0;
}

B. Left and Down

解法

a と b の既約比を考える。その分子と分母がともに k 以下なら 1 回の操作で済む。そうでなければ (1,0) と (0,1) を選べばよく、最大コストは 2 である。

メモ: 最初から long long を使い、必要に応じて int に変更するか考えるのは良い習慣である。

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        long long a, b, k;
        cin >> a >> b >> k;

        if (a == b) {
            cout << 1 << '\n';
            continue;
        }

        long long g = gcd(a, b);
        if (a / g <= k && b / g <= k) {
            cout << 1 << '\n';
        } else {
            cout << 2 << '\n';
        }
    }
    return 0;
}

C. Count Good Numbers

解法

明らかに 1 桁の素数は 2, 3, 5, 7 だけである。したがって、これらの部分集合を列挙して包除原理を使えばよい。

#include <bits/stdc++.h>
using namespace std;

using int64 = long long;

int64 count_not_divisible(int64 x) {
    vector<int> primes = {2, 3, 5, 7};
    int64 res = 0;
    for (int mask = 0; mask < (1 << 4); ++mask) {
        int64 prod = 1;
        int bits = 0;
        for (int i = 0; i < 4; ++i) {
            if (mask & (1 << i)) {
                ++bits;
                prod *= primes[i];
            }
        }
        if (bits % 2 == 0) {
            res += x / prod;
        } else {
            res -= x / prod;
        }
    }
    return res;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        int64 l, r;
        cin >> l >> r;
        cout << count_not_divisible(r) - count_not_divisible(l - 1) << '\n';
    }
    return 0;
}

D. Segments Covering

解法

線形 DP と累積積を使う。各セグメントを右端 r の vector に格納し、l, p, q の 3 つの情報を持たせる。次に (1 - p/q) の mod 上での累積積を計算する。左から右へ走査し、点 i の vector が空でなければ、確率 gl を先に計算し、dp[i] += dp[l-1] * pre[i] * inv(pre[l-1]) * inv(1-gl) * gl とする(各セグメントで覆われ、その区間内に他のセグメントが現れないと仮定)。mod の扱いに注意する。

#include <bits/stdc++.h>
using namespace std;

using int64 = long long;
const int64 MOD = 998244353;

int64 mod_pow(int64 base, int64 exp) {
    int64 res = 1;
    base %= MOD;
    while (exp > 0) {
        if (exp & 1) res = res * base % MOD;
        base = base * base % MOD;
        exp >>= 1;
    }
    return res;
}

int64 mod_inv(int64 x) {
    return mod_pow(x, MOD - 2);
}

struct Segment {
    int l;
    int64 p, q;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    vector<vector<Segment>> byRight(m + 2);
    for (int i = 0; i < n; ++i) {
        int l, r;
        int64 p, q;
        cin >> l >> r >> p >> q;
        byRight[r].push_back({l, p, q});
    }

    vector<int64> pref(m + 2, 0);
    pref[0] = 1;
    for (int i = 1; i <= m; ++i) {
        pref[i] = pref[i - 1];
        for (const auto& seg : byRight[i]) {
            int64 prob = seg.p * mod_inv(seg.q) % MOD;
            pref[i] = pref[i] * ((1 - prob + MOD) % MOD) % MOD;
        }
    }

    vector<int64> dp(m + 2, 0);
    dp[0] = 1;
    for (int i = 1; i <= m; ++i) {
        for (const auto& seg : byRight[i]) {
            int64 prob = seg.p * mod_inv(seg.q) % MOD;
            int64 ratio = pref[i] * mod_inv(pref[seg.l - 1]) % MOD;
            int64 noOther = ratio * mod_inv((1 - prob + MOD) % MOD) % MOD;
            dp[i] = (dp[i] + dp[seg.l - 1] * noOther % MOD * prob) % MOD;
        }
    }

    cout << dp[m] << '\n';
    return 0;
}

タグ: codeforces C++ inclusion-exclusion dynamic-programming prefix-products

10月5日 11:00 投稿