按协议切分 72 位消息,用 128 位乘法计算哈希,并按历史显式代号处理碰撞与发送方优先级。
OJ: shumeng
题目 ID: CSP202506C
难度:普及+/提高-
标签:模拟位运算哈希
日期: 2026-07-31 16:21
形式化题目
每条消息是一个 72 位的二进制串,首比特决定类型:
- 简单消息(首比特
0):三个字段依次为接收方 28 位、发送方 28 位、位置 15 位。每方字段若表示典型代号短码,否则表示 25 位散列值;位置为 0 表示无位置信息。 - 复杂消息(首比特
1):一方为 58 位完整编码,另一方为 12 位散列值,最后 1 位表示谁在前(0 表示完整编码一方是发送方)。
按收到的先后顺序,用此前消息中显式出现的代号推断散列值对应的代号,输出每条消息的文字表示。
思路
解码过程分四个独立环节。
按位切分字段
用一个函数从消息串第 start 位开始连续读取 length 位,逐位左移或上当前位的 0/1 即可。
两种代号的还原
- 完整编码:11 位、每位基数 38,从低位到高位取模还原成字符,再去掉末尾补的空格。
- 典型短码:按题目给定的混合进制(
)反解,得到 5/6 位短代号后再转成完整编码数字。
散列值计算
number * 47055833459 >> (64-n) 的低 n 位。乘积会超过 64 位,必须用 unsigned __int128 计算。
历史表与碰撞
用两个哈希表分别保存"12/25 位散列值
- 消息中显式给出的代号(完整编码或短码)才写入历史表;
- 通过散列值推断出的代号不写入历史表;
- 查表时取的是最近一次出现的记录,天然满足"最后收到的消息优先";
- 同一消息中接收方、发送方都符合时,先记接收方再记发送方,使发送方优先。
代码
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-31 16:21
* update_at: 2026-08-17 22:55
*/
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const ull HASH_MULTIPLIER = 47055833459ULL;
struct DecodedCode {
string name; // 还原出的代号文本,无法还原时为 "###",推断时为 "#"+代号
ull number; // 该代号完整编码对应的数字
bool explicit_code; // 是否为消息中显式给出的完整代号
};
unordered_map<ull, ull> latest_hash_12; // 12 位散列值 -> 最近一次显式出现的完整编码
unordered_map<ull, ull> latest_hash_25; // 25 位散列值 -> 最近一次显式出现的完整编码
// 从 72 位消息串中读取第 start 位起 length 个二进制位(高位在前)
ull read_bits(const string &message, int start, int length) {
ull value = 0;
for (int i = start; i < start + length; i++) {
value = (value << 1) | (ull)(message[i] - '0');
}
return value;
}
char full_digit_to_char(int digit) {
if (digit == 0) return ' ';
if (digit <= 10) return (char)('0' + digit - 1);
if (digit <= 36) return (char)('A' + digit - 11);
return '_';
}
int full_char_to_digit(char value) {
if (value == ' ') return 0;
if (value >= '0' && value <= '9') return value - '0' + 1;
if (value >= 'A' && value <= 'Z') return value - 'A' + 11;
return 37;
}
// 由完整编码数字还原 11 位代号(不足 11 位用空格补齐,去掉末尾空格)
string decode_full_name(ull number) {
string result(11, ' ');
for (int i = 10; i >= 0; i--) {
result[i] = full_digit_to_char((int)(number % 38));
number /= 38;
}
while (!result.empty() && result.back() == ' ') result.pop_back();
return result;
}
// 把 11 位代号编码为完整数字(不足 11 位在结尾补空格)
ull encode_full_name(const string &name) {
ull number = 0;
for (int i = 0; i < 11; i++) {
int digit = 0;
if (i < (int)name.size()) digit = full_char_to_digit(name[i]);
number = number * 38 + digit;
}
return number;
}
char first_short_digit_to_char(int digit) {
if (digit == 0) return ' ';
if (digit <= 10) return (char)('0' + digit - 1);
return (char)('A' + digit - 11);
}
char second_short_digit_to_char(int digit) {
if (digit < 10) return (char)('0' + digit);
return (char)('A' + digit - 10);
}
// 按混合进制反解典型代号的短数字表示,得到 5/6 位短代号
string decode_short_name(ull number) {
ull base26_3 = 26ULL * 26 * 26;
int first = (int)(number / (36ULL * 10 * base26_3));
number %= 36ULL * 10 * base26_3;
int second = (int)(number / (10ULL * base26_3));
number %= 10ULL * base26_3;
int third = (int)(number / base26_3);
number %= base26_3;
int fourth = (int)(number / (26ULL * 26));
number %= 26ULL * 26;
int fifth = (int)(number / 26);
int sixth = (int)(number % 26);
string result;
if (first != 0) result.push_back(first_short_digit_to_char(first));
result.push_back(second_short_digit_to_char(second));
result.push_back((char)('0' + third));
result.push_back((char)('A' + fourth));
result.push_back((char)('A' + fifth));
result.push_back((char)('A' + sixth));
return result;
}
// 计算 n 位散列值:number * 47055833459 右移 64-n 位后取低 n 位。
// 乘积可能超过 64 位,必须用 128 位乘法
ull calculate_hash(ull number, int bits) {
__uint128_t product = (__uint128_t)number * HASH_MULTIPLIER;
ull mask = (1ULL << bits) - 1;
return (ull)((product >> (64 - bits)) & mask);
}
// 根据散列值查表找最近出现的完整编码,找不到返回 false
bool find_by_hash(ull target, int bits, ull &number) {
unordered_map<ull, ull> *table;
if (bits == 12) table = &latest_hash_12;
else table = &latest_hash_25;
unordered_map<ull, ull>::iterator it = table->find(target);
if (it == table->end()) return false;
number = it->second;
return true;
}
void remember_code(ull number) {
latest_hash_12[calculate_hash(number, 12)] = number;
latest_hash_25[calculate_hash(number, 25)] = number;
}
DecodedCode decode_simple_code(ull value) {
DecodedCode result;
if (value >= (1ULL << 25)) {
result.name = decode_short_name(value - (1ULL << 25));
result.number = encode_full_name(result.name);
result.explicit_code = true;
return result;
}
ull number = 0;
result.explicit_code = false;
if (find_by_hash(value, 25, number)) {
result.name = "#" + decode_full_name(number);
result.number = number;
} else {
result.name = "###";
result.number = 0;
}
return result;
}
DecodedCode decode_hash_code(ull value) {
DecodedCode result;
result.explicit_code = false;
ull number = 0;
if (find_by_hash(value, 12, number)) {
result.name = "#" + decode_full_name(number);
result.number = number;
} else {
result.name = "###";
result.number = 0;
}
return result;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
latest_hash_12.reserve(200005);
latest_hash_25.reserve(200005);
int n;
cin >> n;
while (n--) {
string message;
cin >> message;
string receiver;
string sender;
if (message[0] == '0') {
ull receiver_value = read_bits(message, 1, 28);
ull sender_value = read_bits(message, 29, 28);
ull location = read_bits(message, 57, 15);
DecodedCode decoded_receiver = decode_simple_code(receiver_value);
DecodedCode decoded_sender = decode_simple_code(sender_value);
receiver = decoded_receiver.name;
sender = decoded_sender.name;
if (decoded_receiver.explicit_code) {
remember_code(decoded_receiver.number);
}
if (decoded_sender.explicit_code) {
remember_code(decoded_sender.number);
}
cout << receiver << ' ' << sender;
if (location != 0) cout << ' ' << location;
cout << '\n';
} else {
ull first_number = read_bits(message, 1, 58);
ull second_hash = read_bits(message, 59, 12);
int relation = (int)read_bits(message, 71, 1);
DecodedCode decoded_second = decode_hash_code(second_hash);
string first_name = decode_full_name(first_number);
if (relation == 0) {
receiver = decoded_second.name;
sender = first_name;
} else {
receiver = first_name;
sender = decoded_second.name;
}
remember_code(first_number);
cout << receiver << ' ' << sender << '\n';
}
}
return 0;
}复杂度
每条消息解析字段和查表都是常数次操作,总时间复杂度
总结
本题的难点在于字段位数、两种代号编码和碰撞规则,而不是算法。把"切位、还原代号、算哈希、查历史"分别封装成函数后,主流程就只是逐条消息按协议拼装输出。



