P7106 双生独白

文章目录

题目信息

字段 内容
题号 P7106
难度 红题(入门)
知识点 ASCII、字符与数值互转

题目描述

给定一个十六进制颜色码,输出其反色的十六进制颜色码。

颜色码格式形如 #EBA932,由 7 个字符组成:

  • 第 1 位:#,固定不变。
  • 第 2~3 位:R 值(红色分量),用两位十六进制数表示。
  • 第 4~5 位:G 值(绿色分量),用两位十六进制数表示。
  • 第 6~7 位:B 值(蓝色分量),用两位十六进制数表示。

十六进制数码共 16 个,从小到大依次为:0 1 2 3 4 5 6 7 8 9 A B C D E F

对于颜色 (r, g, b),其反色定义为 (255-r, 255-g, 255-b)

输入格式: 一行,长度为 7 的合法十六进制颜色码字符串。

输出格式: 一行,长度为 7 的反色十六进制颜色码字符串。

样例:

样例输入 样例输出
#FFFFFF #000000
#EBA932 #1456CD

样例解释:
#FFFFFF 为例,RGB 值为 (255, 255, 255),反色为 (0, 0, 0),即 #000000
以 B 值 32 为例:(32)_{16} = 50,反色 = 255 - 50 = 205 = (CD)_{16}

数据范围: 本题共 10 个测试点,保证输入为合法十六进制颜色码。

解题思路

先理解「反色」到底在算什么

十六进制颜色码由 3 组「两位十六进制数」构成,每组代表一个 0~255 的颜色分量。
求反色 = 对每个分量求 255 - x

而 255 用十六进制表示恰好是 FF,即求 {FF} - {每位数码}

关键发现{FF} - xy = (15 - x)(15 - y)

也就是说,不用把两位十六进制数先转成十进制再算,直接*对每一位数码单独做「15 减法」*就行!

举例:E 在十六进制中是 14,15 - 14 = 1,对应数码 1
再举:B 是 11,15 - 11 = 4,对应数码 4
所以 EB 的反色就是 14 ✓(和样例吻合)。


算法核心:字符 ↔ 数值互转

题目的难点只有一个:怎么把十六进制数码字符('0'~'9''A'~'F')和它代表的数值(0~15)相互转换?

答案是利用 ASCII 码

字符 ASCII 码 代表的数值
'0' 48 0
'9' 57 9
'A' 65 10
'F' 70 15

可以发现:

  • '0'~'9':数值 = c - '0'
  • 'A'~'F':数值 = c - 'A' + 10,也等于 c - '0' - 7(因为 'A' - '0' = 17,减去 7 得 10)

反过来,数值 → 字符:

  • 0~9:字符 = v + '0'
  • 10~15:字符 = v + 'A' - 10,也等于 v + '0' + 7

代码中用常量 kAsciiGap = 7 来表示这个偏移量,kMaxDigit = 15 代表十六进制单位最大值。


算法流程

读入颜色码(7 个字符)
第 1 位 '#' 直接输出
对第 2~7 位每个字符:
    字符 → 数值(0~15)
    数值 = 15 - 数值
    数值 → 字符
    输出该字符
  • 时间复杂度O(1)(固定处理 6 位数码)
  • 空间复杂度O(1)

完整代码

// P7106 双生独白 - 洛谷
// 知识点:ASCII 字符与十六进制数码互转
// 算法:对每位十六进制数码单独做「15 减法」,利用 ASCII 偏移转换
// 作者:朱雀

#include <iostream>
using namespace std;

// 两段 ASCII 区间之间的偏移量:'A'(65) - '9'(57) - 1 = 7
const int kAsciiGap = 7;
// 单个十六进制数码的最大值(F = 15)
const int kMaxDigit = 15;

// 十六进制数码字符 → 对应的整数值(0~15)
int CharToVal(char c) {
    if (c >= '0' && c <= '9') {
      return c - '0';
    }
    // 'A'~'F':减去 '0' 后还需减去间隔 7
    return c - '0' - kAsciiGap;
}

// 整数值(0~15)→ 对应的十六进制数码字符
char ValToChar(int v) {
    if (v <= 9) {
      return v + '0';
    }
    // 10~15:加上 '0' 后还需加上间隔 7
    return v + '0' + kAsciiGap;
}

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

    char color_code[7];
    for (int i = 0; i < 7; i++) {
      cin >> color_code[i];
    }

    // 第 1 位 '#' 直接输出,不参与计算
    cout << color_code[0];

    // 对第 2~7 位,逐位做「15 - 数码值」运算
    for (int i = 1; i < 7; i++) {
      cout << ValToChar(kMaxDigit - CharToVal(color_code[i]));
    }
    cout << "\n";

    return 0;
}

关键代码讲解

kAsciiGap = 7 的由来

在 ASCII 表中,'9' 的编码是 57,'A' 的编码是 65,两者之间相差 8,但 '9''A' 之间还有 :;<=>?@ 七个字符(并非数字也非大写字母),所以从 '9' 跳到 'A' 需要跨过 7 个非数码字符

  • CharToVal('A') = 'A' - '0' - 7 = 65 - 48 - 7 = 10
  • ValToChar(10) = 10 + '0' + 7 = 10 + 48 + 7 = 65 = 'A'

为什么不需要把两位十六进制数合并再算?

255 的十六进制是 {FF},而 {FF} - {XY}(两位十六进制数)恰好等于 (15-X)(15-Y)。每一位可以独立计算,完全不需要先合并成十进制。这是本题最核心的数学技巧。

为什么加了 ios::sync_with_stdio(false)

虽然本题数据量极小,但这是标准 C++ 竞赛模板的一部分。关闭 C/C++ 流同步后,cin/cout 速度会大幅提升,在处理大量输入时有明显效果。养成习惯,无害。

易错点 / 注意事项

  • ⚠️ # 要原样输出:第一个字符是 #,不参与任何计算,忘记输出就会 WA。
  • ⚠️ 大写字母:题目明确说字母均为大写,CharToValValToChar 只处理大写 A~F,不要混入小写。
  • ⚠️ 别用 map 硬编码映射:有些同学会用 map<char, char> 枚举所有 16 种互换关系,虽然也能 AC,但远不如 ASCII 偏移简洁,也不利于理解。
  • ⚠️ 末尾换行:输出完 7 个字符后记得换行,部分测试点对此敏感。

其他语言解法

以下各语言解法思路完全一致:逐位做「15 减法」,仅写法不同。均遵循对应语言的 Google 开发规范。

Python

Python 内置 int(c, 16) 可直接将十六进制字符转为数值,format(v, 'X') 可将数值转回大写十六进制字符,无需手动处理 ASCII 偏移,代码极为简洁。

# P7106 双生独白 - 洛谷
# 知识点:十六进制字符与数值互转
# 算法:对每位数码单独做「15 减法」

_MAX_DIGIT = 15


def char_to_val(c: str) -> int:
    """十六进制数码字符 → 整数值(0~15)。"""
    return int(c, 16)


def val_to_char(v: int) -> str:
    """整数值(0~15)→ 大写十六进制数码字符。"""
    return format(v, 'X')


def main() -> None:
    color_code = input().strip()
    result = [color_code[0]]  # '#' 原样保留
    for ch in color_code[1:]:
        result.append(val_to_char(_MAX_DIGIT - char_to_val(ch)))
    print(''.join(result))


if __name__ == '__main__':
    main()

Java

Java 同样可以用 Character.digit(c, 16)Character.forDigit(v, 16) 处理十六进制字符,但为了与 C++ 思路对应,这里手动实现转换函数,便于理解 ASCII 偏移原理。

⚠️ 洛谷 Java 提交规范:洛谷要求公开类名必须为 Main,否则编译器找不到入口类会直接报错。

// P7106 双生独白 - 洛谷
// 知识点:ASCII 字符与十六进制数码互转
// 算法:对每位十六进制数码单独做「15 减法」

import java.util.Scanner;

public class Main {

    private static final int ASCII_GAP = 7;
    private static final int MAX_DIGIT = 15;

    /** 十六进制数码字符 → 整数值(0~15)。 */
    private static int charToVal(char c) {
        if (c >= '0' && c <= '9') {
            return c - '0';
        }
        return c - '0' - ASCII_GAP;
    }

    /** 整数值(0~15)→ 大写十六进制数码字符。 */
    private static char valToChar(int v) {
        if (v <= 9) {
            return (char) (v + '0');
        }
        return (char) (v + '0' + ASCII_GAP);
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String colorCode = scanner.next();

        StringBuilder result = new StringBuilder();
        result.append(colorCode.charAt(0)); // '#' 原样保留
        for (int i = 1; i < colorCode.length(); i++) {
            result.append(valToChar(MAX_DIGIT - charToVal(colorCode.charAt(i))));
        }
        System.out.println(result);
    }
}

Go

Go 遵循 Google 内部风格(即官方 gofmt 规范):包名小写,函数名 PascalCase(导出)或 camelCase(包内),用 fmt.Scan 读取输入。

// P7106 双生独白 - 洛谷
// 知识点:ASCII 字符与十六进制数码互转
// 算法:对每位十六进制数码单独做「15 减法」

package main

import "fmt"

const (
    asciiGap = 7  // '9' 与 'A' 之间非数码字符的数量
    maxDigit = 15 // 单个十六进制数码的最大值
)

// charToVal 将十六进制数码字符转换为整数值(0~15)。
func charToVal(c byte) int {
    if c >= '0' && c <= '9' {
        return int(c - '0')
    }
    return int(c-'0') - asciiGap
}

// valToChar 将整数值(0~15)转换为大写十六进制数码字符。
func valToChar(v int) byte {
    if v <= 9 {
        return byte(v) + '0'
    }
    return byte(v) + '0' + asciiGap
}

func main() {
    var colorCode string
    fmt.Scan(&colorCode)

    result := make([]byte, len(colorCode))
    result[0] = colorCode[0] // '#' 原样保留
    for i := 1; i < len(colorCode); i++ {
        result[i] = valToChar(maxDigit - charToVal(colorCode[i]))
    }
    fmt.Println(string(result))
}

JavaScript (Node.js)

JavaScript 可直接用 parseInt(c, 16)v.toString(16).toUpperCase() 完成转换,比手动 ASCII 偏移更简洁。以下代码在 Node.js 环境下读取标准输入。

// P7106 双生独白 - 洛谷
// 知识点:十六进制字符与数值互转
// 算法:对每位数码单独做「15 减法」

'use strict';

const MAX_DIGIT = 15;

/**
 * 将十六进制数码字符转换为整数值(0~15)。
 * @param {string} c - 单个十六进制数码字符
 * @returns {number}
 */
function charToVal(c) {
    return parseInt(c, 16);
}

/**
 * 将整数值(0~15)转换为大写十六进制数码字符。
 * @param {number} v - 0~15 的整数
 * @returns {string}
 */
function valToChar(v) {
    return v.toString(16).toUpperCase();
}

/**
 * 计算十六进制颜色码的反色。
 * @param {string} colorCode - 形如 '#RRGGBB' 的颜色码
 * @returns {string} 反色颜色码
 */
function invertColor(colorCode) {
    let result = colorCode[0]; // '#' 原样保留
    for (let i = 1; i < colorCode.length; i++) {
        result += valToChar(MAX_DIGIT - charToVal(colorCode[i]));
    }
    return result;
}

// 读取标准输入(适用于 OJ 环境)
const lines = [];
process.stdin.on('data', (data) => lines.push(data.toString().trim()));
process.stdin.on('end', () => {
    console.log(invertColor(lines[0]));
});