模拟十进制舍入:每步把当前数舍入到 10 的幂,逢 5 进位;低位进位会改写高位,必须对当前数连锁进位。
OJ: roj
题目 ID: 19996
难度:普及-
标签:模拟数学
日期: 2026-08-28 19:47
形式化题目
给定 k 位十进制整数 n。依次执行 k-1 次操作,第 j 次操作针对当前数从右数第 j 位:
- 若该位数字
:将该位及其右侧所有位全部置 0; - 若该位数字
:同样全部置 0,并向左一位进 1;进位按十进制加法连锁传播(某位达到 10 即向更高位进 1),允许最高位进位使数位增加;数位增加后仍只执行剩余的 k-1 次操作。
要求输出初始数及每次操作后的数,共 k 个数,用 " -> " 连接(k = 1 时只输出初始数)。
思路
一句话本质:每一步都是把"当前的数"舍入到
问题? 每一步具体做了什么?
看最低位:数字
问题? 进位会改写高位,后面的步骤会不会因此受影响?
会,而且这正是本题最关键的坑。以 14995 为例:最低位 5 进位后,9+1=10 连锁进位,把千位的 4 改写成 5;之后处理千位时,看到的数字是 5 而不是原始数字 4,还要再进位一次。所以绝不能拿原始数字逐位独立判断——每一步看到的都是"当前数"。
问题? 进位会连锁发生吗?
会。299997 的最低位 7 进位后,一路 9+1=10 连锁,直接变成 300000。所以进 1 之后要用 while 循环把超过 9 的位逐位归一化(本位减 10、更高位加 1),直到全部小于 10。
问题? 最高位进位会把数变长吗?变长后怎么处理?
会,例如 95 -> 100:十位 9+1=10 后继续进位,新开一个百位。题目说"位数编号规则不变",意思是不管数变多长,仍然只做原来的 k-1 步,新增的最高位不参与后续步骤。实现上给数组多留几个高位空位即可。
问题? 数字最多 100 位,装不进整数,怎么模拟?
字符串读入,逆序存进 int 数组(个位在 0 号位),预留高位空间;每一步:若第 p 位
先看一个小数据版本,它直接按题意用 long long 整数模拟,把"连锁进位"完全交给整数加法:
/**
* 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-08-28 19:08
* update_at: 2026-08-28 19:08
*/
// brute.cpp:小数据暴力解,直接按题意逐步"把当前数舍入到 10^j 的倍数"。
// 用 long long 整数模拟:舍去低位 = 除以 10^j 再乘回;第 j 位 >= 5 就再 +10^j。
// 连锁进位由整数加法自动完成,是最贴近题意的写法。
// 只适合 k <= 15 左右的小数据(对拍用),k 大时数装不进 long long。
#include <bits/stdc++.h>
using namespace std;
int k;
string s;
// 计算 10^e
long long pow10(int e) {
long long res = 1;
for (int i = 0; i < e; i++) res *= 10;
return res;
}
void solve() {
cin >> k >> s;
// 字符串转成整数(小数据才装得下)
long long n = 0;
for (int i = 0; i < k; i++) n = n * 10 + (s[i] - '0');
cout << n;
for (int j = 1; j <= k - 1; j++) { // 第 j 步处理从右数第 j 位
long long p = pow10(j); // 10^j
long long digit = (n / (p / 10)) % 10; // 当前数从右数第 j 位的数字
if (digit >= 5)
n = (n / p + 1) * p; // 舍去低位并向前进 1(进位自动连锁)
else
n = (n / p) * p; // 只舍去低位
cout << " -> " << n;
}
cout << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) solve();
return 0;
}暴力版与正解是同一个模拟:小数据装得进整数,进位就由整数加法自动完成;正解因为 k <= 100 装不进整数,改用数组手动进位,流程一字不差。
下面这张表展示 14995 的完整过程,注意千位 4 如何被最低位的进位改写成 5:
| 步骤 | 处理位(从右数) | 处理前数字 | 该位数字 | 动作 |
|---|---|---|---|---|
| 1 | 第 1 位 | 14995 | 5 | |
| 2 | 第 2 位 | 15000 | 0 | |
| 3 | 第 3 位 | 15000 | 0 | |
| 4 | 第 4 位 | 15000 | 5 |
从表中看两件事:一是第 1 步的连锁进位把千位 4 改写成 5;二是第 4 步处理的正是改写后的 5,于是又进了一次位。这就是"必须对当前数模拟"的直接证据。
代码
/**
* 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-08-28 19:08
* update_at: 2026-08-28 19:08
*/
// A. Number:按位四舍五入的数组模拟。
// 逆序存数字(a[0] 是个位),每步对"当前数"的第 p 位判断:
// 该位 >= 5 就向前一位进 1 并连锁进位,然后把第 p 位及以下全部置 0。
#include <bits/stdc++.h>
using namespace std;
const int MAXK = 105;
int k;
int a[MAXK + 5]; // a[0] 是个位,逆序存放数字,多留几个位置给最高位进位
// 输出当前数:从最高非零位开始,跳过前导零。
void print_num() {
int top = MAXK + 4;
while (top > 0 && a[top] == 0) top--;
for (int i = top; i >= 0; i--) cout << a[i];
}
void solve() {
string s;
cin >> k >> s;
memset(a, 0, sizeof(a));
for (int i = 0; i < k; i++) a[i] = s[k - 1 - i] - '0';
print_num();
for (int p = 0; p < k - 1; p++) { // 从个位向高位依次处理 k-1 步
// 第 p 位 >= 5:舍去后向前一位进 1
if (a[p] >= 5) {
a[p + 1]++;
// 连锁进位:某位超过 9 就继续向更高位进,直到全部小于 10
int j = p + 1;
while (a[j] >= 10) {
a[j + 1] += a[j] / 10;
a[j] %= 10;
j++;
}
}
a[p] = 0; // 舍去第 p 位(更低的位在之前步骤已全部为 0)
cout << " -> ";
print_num();
}
cout << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) solve();
return 0;
}复杂度
每步的判断与置 0 是
总结
本题是典型的"带状态按位模拟":状态就是当前数,步骤顺序固定,没有需要推导的数学结论。三个易错点:
- 对当前数判断——低位进位会改写高位,后续步骤看到的是改写后的数字(14995 -> 20000);
- 进位连锁归一化——某位达到 10 必须继续向上进位,直到全部小于 10(299997 -> 300000);
- 最高位预留空间——进位可能使数变长(95 -> 100),数组要多开几个位置,且数位编号规则不变。
小数据先写整数模拟(brute.cpp)验证题意理解,再用数组版实现满分做法,二者对拍一致,是最稳的做题节奏。
