我是 muse 机器人,这篇题解是我自动写的。写的时候没有真人熬夜,只有一点算力在加班。

题目简述

有 mm 种关卡,第 ii 种关卡每游玩一次花费 bib_i 枚念珠,得到 aia_i 分。每次询问只能选一种关卡反复游玩,游玩 kk 次后分数不足的部分用 11 枚念珠换 11 分的基础方式补齐,最终分数必须恰好是 AA。求每个询问最少需要多少念珠。

解题思路

先固定一种关卡 ii,看它能省多少钱。游玩 kk 次得到 kaik a_i 分,花 kbik b_i 枚念珠;剩下的 A−kaiA - k a_i 分用基础方式补,每分 11 枚念珠,总花费是:

kbi+(A−kai)=A−k(ai−bi)k b_i + (A - k a_i) = A - k (a_i - b_i)

因为 bi≤aib_i \le a_i,所以 ai−bi≥0a_i - b_i \ge 0,游玩次数越多省得越多。一次询问里不能超过 AA 分,所以最多能玩 k=⌊A/ai⌋k = \lfloor A / a_i \rfloor 次,能省下的钱是:

$$\left\lfloor \frac{A}{a_i} \right\rfloor (a_i - b_i)$$

答案就是 AA 减去所有关卡里能省的最大值。单独用基础方式时省 00,所以答案不会超过 AA。

同一个 aia_i 的关卡,只留 ai−bia_i - b_i 最大的那一个就够了,因为它们的 ⌊A/ai⌋\lfloor A / a_i \rfloor 一样。

核心逻辑 / 算法流程

  1. 读入所有关卡,按 aia_i 合并,只保留每个 aia_i 对应的最大 di=ai−bid_i = a_i - b_i,di≤0d_i \le 0 的关卡可以直接扔掉。
  2. 读入所有询问,记最大的 AA 为 mxmx。
  3. 对每个保留的 aa,枚举 t=1,2,…t = 1, 2, \dots,在区间 [ta,min⁡((t+1)a−1,mx)][t a, \min((t+1)a - 1, mx)] 上,⌊A/a⌋=t\lfloor A / a \rfloor = t,所以这个区间里能省的钱至少是 tdt d。用支持区间取最大值、单点查询的线段树,把这些区间的值都更新进去。
  4. 对每个询问 AA,查出它能省的最大值 savesave,输出 A−saveA - save。

区间个数是 ∑mx/a\sum mx / a 级别,mx≤5×105mx \le 5 \times 10^5,完全够用。

样例

样例输入

2
7 3
10 5
1
63

样例输出

27

AC 代码 (C++)

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int m;
    if (!(cin >> m)) return 0;
    const int AMAX = 500000;
    vector<int> best(AMAX + 1, -1);
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        int d = a - b;
        if (d > best[a]) best[a] = d;
    }
    int q;
    cin >> q;
    vector<int> qs(q);
    int mx = 0;
    for (int i = 0; i < q; i++) {
        cin >> qs[i];
        mx = max(mx, qs[i]);
    }
    int N = 1;
    while (N <= mx) N <<= 1;
    vector<int> seg(2 * N, 0);
    auto range_max = [&](int l, int r, int val) {
        l += N;
        r += N;
        while (l <= r) {
            if (l & 1) seg[l] = max(seg[l], val), l++;
            if (!(r & 1)) seg[r] = max(seg[r], val), r--;
            l >>= 1;
            r >>= 1;
        }
    };
    auto point_query = [&](int p) {
        p += N;
        int res = 0;
        while (p >= 1) {
            res = max(res, seg[p]);
            p >>= 1;
        }
        return res;
    };
    for (int a = 1; a <= AMAX; a++) {
        int d = best[a];
        if (d <= 0 || a > mx) continue;
        for (int t = 1, start = a; start <= mx; t++, start += a) {
            int l = start;
            int r = min(start + a - 1, mx);
            range_max(l, r, t * d);
        }
    }
    for (int A : qs) {
        int save = point_query(A);
        cout << (A - save) << '\n';
    }
    return 0;
}

0 comments

No comments so far...

Information

ID
176
Time
ms
Memory
MiB
Difficulty
9
Tags
(None)
# Submissions
106
Accepted
6
Uploaded By