题解:洛谷 P1553 数字反转(升级版)

【题目来源】

洛谷:P1553 数字反转(升级版) - 洛谷

【题目描述】

给定一个数,请将该数各个位上数字反转得到一个新数。

这次与 NOIP2011 普及组第一题不同的是:这个数可以是小数,分数,百分数,整数。

  • 整数反转是将所有数位对调。
  • 小数反转是把整数部分的数反转,再将小数部分的数反转,不交换整数部分与小数部分。
  • 分数反转是把分母的数反转,再把分子的数反转,不交换分子与分母。
  • 百分数的分子一定是整数,百分数只改变数字部分。

【输入】

一个实数 \(s\)

【输出】

一个实数,即 \(s\) 的反转数

【输入样例】

700/27

【输出样例】

7/72

【核心思想】

  1. 问题分析:给定一个数,可能是整数、小数、分数或百分数,要求将数字部分(整数部分、分子、分母等)按位反转,并保持原格式(如小数点、分数线、百分号的位置不变)。整数反转需去掉前导零(反转后可能产生前导零,需去除);小数反转需分别反转整数部分和小数部分,并处理小数部分末尾的零;分数反转需分别反转分子和分母;百分数只反转数字部分。本质是一个字符串解析与分段反转问题。

  2. 算法选择

    • 分段处理:根据输入字符串的特征(包含 ./% 或纯数字)判断类型,然后分割成对应的部分,分别调用统一的“反转并去除前导零”函数处理,最后按原格式拼接输出。
    • 反转函数:设计 f0(string s),对数字字符串进行反转,并从最高位开始去除前导零(若全为零则返回 "0")。
  3. 关键步骤

    • 读入:读取字符串 s
    • 分类处理
      • 整数:直接调用 f0(s)
      • 小数:按 . 分割为整数部分 s1 和小数部分 s2。整数部分调用 f0,小数部分逆序反转并去除末尾的零(即反转后前面的零),然后拼接。
      • 分数:按 / 分割为分子 s1 和分母 s2,分别调用 f0,中间用 / 连接。
      • 百分数:按 % 分割,数字部分调用 f0,末尾加 %
    • 输出:输出处理后的字符串。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),每个字符处理常数次。
    • 空间复杂度:\(O(n)\),存储分割后的子串。
  5. 字符串分割与反转

    • 统一反转函数f0 负责反转字符串并去除前导零(对于整数部分和分子分母),这是代码复用的关键。
    • 小数部分的特殊处理:小数部分反转时,需要保留有效数字,即去除反转后末尾的零(对应原数前面的零)。代码中通过找到第一个非零位置 mark,然后从末尾到 mark 输出。
    • 格式保留:处理过程中不改变分隔符(./%)的位置和数量。
    • 边界情况:如 00.00% 等,需确保输出 0 而非空字符串。
    • 适用场景:这类“数字反转升级版”是字符串处理的综合题,考察分割、反转、去零等操作。

【算法标签】

普及- #字符串入门

【代码详解】

#include <bits/stdc++.h>  // 包含标准库头文件(万能头文件)
using namespace std;      // 使用标准命名空间

/**
 * 翻转整数部分
 * @param s 要翻转的数字字符串
 */
void f0(string s)
{
    int len = s.length();  // 获取字符串长度
    int mark;              // 标记第一个非零字符的位置

    // 从后往前找到第一个非零字符
    for (mark = len - 1; mark >= 0; mark--)
    {
        if (s[mark] != '0')
            break;
    }

    // 如果全是零,输出0并返回
    if (mark < 0)
    {
        cout << 0;
        return;
    }

    // 从第一个非零字符开始倒序输出
    for (int i = mark; i >= 0; i--)
    {
        cout << s[i];
    }
}

/**
 * 翻转小数部分
 * @param s 要翻转的小数字符串
 */
void f1(string s)
{
    int m = s.find(".");            // 找到小数点位置
    int len = s.length();          // 获取字符串长度
    string s1 = s.substr(0, m);    // 提取整数部分
    string s2 = s.substr(m + 1, len - m - 1);  // 提取小数部分

    f0(s1);                         // 翻转整数部分
    cout << '.';                    // 输出小数点

    // 处理小数部分前导零
    int mark = 0;
    for (mark = 0; mark < s2.length(); mark++)
    {
        if (s2[mark] != '0')
            break;
    }

    // 如果小数部分全是零,输出0并返回
    if (mark >= s2.length())
    {
        cout << 0;
        return;
    }

    // 翻转小数部分(去掉末尾的零)
    for (int i = s2.length() - 1; i >= mark; i--)
    {
        cout << s2[i];
    }
}

/**
 * 翻转分数部分
 * @param s 要翻转的分数字符串
 */
void f2(string s)
{
    int m = s.find("/");            // 找到分数线位置
    int len = s.length();           // 获取字符串长度
    string s1 = s.substr(0, m);     // 提取分子部分
    string s2 = s.substr(m + 1, len - m - 1);  // 提取分母部分

    f0(s1);                         // 翻转分子部分
    cout << '/';                    // 输出分数线
    f0(s2);                         // 翻转分母部分
}

/**
 * 翻转百分数部分
 * @param s 要翻转的百分数字符串
 */
void f3(string s)
{
    int m = s.find("%");            // 找到百分号位置
    int len = s.length();           // 获取字符串长度
    string s1 = s.substr(0, m);    // 提取数字部分

    f0(s1);                         // 翻转数字部分
    cout << '%';                    // 输出百分号
}

int main()
{
    string s;                       // 定义字符串:存储输入的数字
    cin >> s;                       // 输入数字字符串

    // 根据数字类型调用对应的处理函数
    if (s.find("/") != -1)          // 如果是分数
    {
        f2(s);
    }
    else if (s.find("%") != -1)      // 如果是百分数
    {
        f3(s);
    }
    else if (s.find(".") != -1)     // 如果是小数
    {
        f1(s);
    }
    else                             // 如果是整数
    {
        f0(s);
    }

    return 0;                       // 程序正常结束
}

【运行结果】

700/27
7/72
posted @ 2026-08-30 09:19  团爸讲算法  阅读(26)  评论(0)    收藏  举报