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。 - ⚠️ 大写字母:题目明确说字母均为大写,
CharToVal和ValToChar只处理大写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]));
});