小白月赛47 解説と実装

A. ボール詰めゲーム

円柱に詰め込めるボールの体積を求める。円柱の体積は πr²h、各ボールの体積は 4/3 πr³。高さ h の中に収まるボール数は ⌊h/(2r)⌋ 個なので、答えは

πr²h − ⌊h/(2r)⌋ · 4/3 πr³

である。計算量は O(1)。

#include <bits/stdc++.h>
using namespace std;
const double PI = 3.14159265358979323846;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T; cin >> T;
    while (T--) {
        double r, h; cin >> r >> h;
        int cnt = floor(h / (2 * r));
        double vol = PI * r * r * h - cnt * 4.0 / 3 * PI * r * r * r;
        cout << fixed << setprecision(3) << vol << '\n';
    }
    return 0;
}

B. 数値の集合

与えられた配列の積を 10⁹+7 で割った余りを出力。算術・幾何平均不等式より、積が最小値となるためにはすべての要素を 1 グループにまとめればよい。計算量 O(n)。

MOD = 10**9 + 7
n = int(input())
a = list(map(int, input().split()))
prod = 1
for v in a:
    prod = (prod * v) % MOD
print(prod)

C. 子猫の列

両端から取り除きながら、末尾に追加される値 an+1 以下の要素をまとめて削除する操作を繰り返す。deque を用いて双方向から効率的に削除し、操作回数を数える。計算量 O(n)。

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

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n; cin >> n;
    vector<int> a(n + 1);
    for (int i = 0; i <= n; ++i) cin >> a[i];
    deque<int> dq(a.begin(), a.begin() + n);
    int res = 0;
    while (!dq.empty()) {
        int skip = 0;
        while (!dq.empty() && dq.back() <= a[n]) {
            dq.pop_back();
            ++skip;
        }
        if (dq.empty()) {
            res += skip + 1;
            break;
        }
        dq.pop_back(); ++res;
        if (!dq.empty()) dq.pop_front();
    }
    cout << res;
    return 0;
}

D. 文字列橋

文字列を連結して最長の文字列を作る問題。末尾文字が次の先頭文字と一致する場合のみ連結できる。dp[i] を「i 番目の文字列で終わる最長長さ」とし、各文字 c について「c で終わる最大値」を保持して更新。計算量 O(n)。

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

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T; cin >> T;
    while (T--) {
        int n; cin >> n;
        int best[128] = {0};
        int ans = 0;
        while (n--) {
            string s; cin >> s;
            int len = s.size();
            int head = s[0], tail = s.back();
            int cur = best[head] + len;
            ans = max(ans, cur);
            best[tail] = max(best[tail], cur);
        }
        cout << ans << '\n';
    }
    return 0;
}

E. カラーグリッド

各色が覆う最小矩形を求め、二次元累積和で各セルが何色に覆われているかをカウント。覆われていないセルの数を答える。計算量 O(nm + 10⁶)。

#include <bits/stdc++.h>
using namespace std;
const int MAXC = 1e6 + 5;
const int MAXN = 1e3 + 5;

struct Rect {
    int x1 = MAXN, x2 = 0, y1 = MAXN, y2 = 0;
    void add(int x, int y) {
        x1 = min(x1, x); x2 = max(x2, x);
        y1 = min(y1, y); y2 = max(y2, y);
    }
    bool valid() const { return x1 <= x2 && y1 <= y2; }
};

Rect color[MAXC];
int diff[MAXN][MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m; cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) {
            int c; cin >> c;
            color[c].add(i, j);
        }
    for (int c = 0; c < MAXC; ++c) {
        if (!color[c].valid()) continue;
        int x1 = color[c].x1, x2 = color[c].x2;
        int y1 = color[c].y1, y2 = color[c].y2;
        diff[x1][y1]++; diff[x1][y2 + 1]--;
        diff[x2 + 1][y1]--; diff[x2 + 1][y2 + 1]++;
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) {
            diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];
            if (diff[i][j] == 0) ++ans;
        }
    cout << ans;
    return 0;
}

F. ロープウェイ

木構造上で指定された観光地を結ぶ新規路線を 1 本追加し、全ての指定地を根 k から到達可能にする最小コストを求める。指定地が k の部分木に含まれない場合はそれらの LCA に向かう辺を追加する。計算量 O(n log n)。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 2e5 + 5;
const int LOG = 20;

vector<int> g[MAXN];
int dep[MAXN], up[MAXN][LOG];
bool need[MAXN];
ll cnt[MAXN];

void dfs(int u, int p) {
    up[u][0] = p;
    for (int i = 1; i < LOG; ++i)
        up[u][i] = up[up[u][i - 1]][i - 1];
    for (int v : g[u]) if (v != p) {
        dep[v] = dep[u] + 1;
        dfs(v, u);
        cnt[u] += cnt[v];
    }
}

int lca(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    for (int i = LOG - 1; i >= 0; --i)
        if (dep[u] - (1 << i) >= dep[v])
            u = up[u][i];
    if (u == v) return u;
    for (int i = LOG - 1; i >= 0; --i)
        if (up[u][i] != up[v][i])
            u = up[u][i], v = up[v][i];
    return up[u][0];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n; cin >> n;
    for (int i = 1; i < n; ++i) {
        int u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    int m, k; cin >> m >> k;
    for (int i = 0, x; i < m; ++i) {
        cin >> x; need[x] = true;
    }
    dfs(k, k);

    int bad = -1;
    for (int i = 1; i <= n; ++i)
        if (need[i] && cnt[i] == 0) {
            if (bad == -1) bad = i;
            else bad = lca(bad, i);
        }

    ll sum = 0;
    if (bad != -1) {
        for (int i = 1; i <= n; ++i)
            if (need[i]) {
                if (cnt[i]) sum += dep[i] - dep[k];
                else sum += dep[i] - dep[bad] + 1;
            }
        cout << sum;
    } else {
        for (int i = 1; i <= n; ++i)
            if (need[i]) sum += dep[i] - dep[k];
        ll best = sum;
        function<void(int, int)> dfs2 = [&](int u, int p) {
            if (u != k && cnt[u])
                best = min(best, sum - cnt[u] * (dep[u] - dep[k] - 1));
            for (int v : g[u]) if (v != p) dfs2(v, u);
        };
        dfs2(k, k);
        cout << best;
    }
    return 0;
}

タグ: 数学 几何 双指针 动态规划 差分数组

7月14日 22:37 投稿