Roundabout Rounding

GitHub跳转原题关系图返回列表

找出链式舍入与直接舍入不同的数都落在每个位数的区间 (444...4,499...9] 中。

OJ: usaco

题目 ID: 1443

难度:普及-

标签:数学模拟

日期: 2026-07-11 12:32

题意

给定一个整数 xx,令 PP 为满足 10Px10^P \geqslant x 的最小整数。

有两种舍入方式:

  • 直接舍入:一次性把 xx 舍入到最近的 10P10^P
  • 链式舍入:先舍入到 10110^1,再舍入到 10210^2,一直到 10P10^P

对每个询问 NN,要求统计 2xN2 \leqslant x \leqslant N 中有多少个数,两种舍入结果不同。

思路

暴力模拟

可以先写出直接舍入和链式舍入,然后枚举每个 x 检查。

这个暴力适合小数据,也适合验证最终公式:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 12:32
 * update_at: 2026-07-11 12:33
 */
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

long long round_to(long long x, long long base) {
    long long rem = x % base;
    if (rem < base / 2) {
        return x - rem;
    }
    return x + base - rem;
}

long long chain_round(long long x, long long target) {
    for (long long base = 10; base <= target; base *= 10) {
        x = round_to(x, base);
    }
    return x;
}

bool is_different(long long x) {
    long long target = 1;
    while (target < x) {
        target *= 10;
    }
    return chain_round(x, target) != round_to(x, target);
}

long long solve_one(long long n) {
    long long ans = 0;
    for (long long x = 2; x <= n; x++) {
        if (is_different(x)) {
            ans++;
        }
    }
    return ans;
}

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

    int t;
    cin >> t;
    while (t--) {
        long long n;
        cin >> n;
        cout << solve_one(n) << '\n';
    }

    return 0;
}

暴力的瓶颈是要枚举 2..N,而 NN 最大到 10910^9

观察规律

先看两位数。

48 链式舍入时:

text
48 -> 50 -> 100

但直接舍入到 100 时,因为十位是 4,结果是 0

所以 454945 \dots 49 都会产生差异,而 44 不会,50 也不会。

三位数时类似:

text
445..499

四位数时是:

text
4445..4999

也就是说,长度为 LL 的有效数正好落在:

text
(444...4, 499...9]

左端点 444...4 不计入,右端点 499...9 计入。

区间计数

对每一种位数构造:

text
lower = 444...4
upper = 499...9

当前位数对答案的贡献是:

text
max(0, min(N, upper) - lower)

注意这里没有 +1,因为 lower 本身不计入。

由于 N109N \leqslant 10^9,枚举 10 位以内的区间就够了。

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 12:32
 * update_at: 2026-07-11 12:33
 */
#include <bits/stdc++.h>
using namespace std;

long long solve_one(long long n) {
    long long ans = 0;
    long long lower = 0; // 当前位数下的 444...4,这个端点本身不算
    long long pow10 = 1;

    for (int len = 1; len <= 10; len++) {
        lower = lower * 10 + 4;
        long long upper = 5 * pow10 - 1; // 当前位数下的 499...9
        long long high = min(n, upper);
        if (high > lower) {
            ans += high - lower;
        }
        pow10 *= 10;
    }

    return ans;
}

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

    int t;
    cin >> t;
    while (t--) {
        long long n;
        cin >> n;
        cout << solve_one(n) << '\n';
    }

    return 0;
}

复杂度

每个询问只枚举常数个十进制位数,时间复杂度 O(logN)O(\log N)

空间复杂度 O(1)O(1)

总结

这题的核心不是把舍入过程模拟得更快,而是找到会产生差异的数的形态。

链式舍入会从低位把一串 4 推出进位,所以每个位数只需要统计 (444...4,499...9] 这个区间。