HTTP 头信息

恢复 Huffman 树并解码字段字符串,用双端队列模拟动态表的前插和容量淘汰。

OJ: shumeng

题目 ID: CSP202509C

难度:普及+/提高-

标签:模拟字符串二叉树队列

日期: 2026-07-31 16:21

形式化题目

给定 SS 条静态表条目、动态表容量 DD、Huffman 树的先序描述串,以及 NN 条 HPACK 指令。每条指令输出一行 key: value

  • 1 i:表格引用,直接输出编号 ii 的条目;
  • 2 0 k v2 i v:字面量并索引,输出后把该键值对插入动态表;
  • 3 0 k v3 i v:字面量不索引,只输出,不更新动态表。

字段名或字段值既可以是普通字符串,也可以是 Huffman 编码串,都需要先解码。动态表满时,插入新条目会淘汰最旧的条目。

思路

整个解码过程拆成三个独立环节:恢复 Huffman 树、解码字符串、维护两张表。

恢复 Huffman 树

描述串按先序给出:0 表示内部节点,之后递归描述左右子树;1 表示叶子节点,后面紧跟一个字符。用一个全局游标 pos 顺序读取,递归即可还原整棵树。

例如样例中的 001b01c1d1a 对应下面这棵树:

text
        *
       / \
      *   a
     / \
    b   *
       / \
      c   d

从根往下走,左走记 0、右走记 1,叶子上的字符就是该编码对应的字符。例如 a 的编码是 1b 的编码是 00

解码字符串

  • 普通字符串:开头不是 H 时原样输出;开头是 HH 时去掉开头的第一个 H
  • Huffman 串:H 后跟偶数个十六进制字符,最后一个字节表示末尾补零个数 pp。其余字节按高位在前逐位读取,从根节点沿树走到叶子就得到一个字符,然后回到根继续。

维护两张表

  • 静态表按输入顺序保存,编号为 1S1 \sim S
  • 动态表用 deque 保存,队首是最新插入的条目;编号大于 SS 时换算成动态表下标。
  • 字面量并索引指令把新条目插入队首,若已达到容量上限,先淘汰队尾最旧的条目。

代码

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 19:23
 */
#include <bits/stdc++.h>
using namespace std;

// Huffman 树节点:用数组下标代替指针,left/right 为子节点下标,-1 表示无子节点
struct HuffmanNode {
    int left;
    int right;
    char value;   // 叶子节点存储的字符
    bool leaf;    // 是否为叶子节点
};

// 表格中的一条键值对条目
struct TableEntry {
    string key;
    string value;
};

vector<HuffmanNode> huffman;      // Huffman 树,下标 0 为根节点
string tree_desc;                 // Huffman 树的先序描述串:0 内部节点,1 叶子+字符
int pos;                          // 递归解析描述串时当前读取到的位置
vector<TableEntry> static_table;  // 静态表,按输入顺序保存
deque<TableEntry> dynamic_table;  // 动态表,队首为最新插入的条目
int dynamic_limit;                // 动态表容量上限

// 按先序恢复 Huffman 树:读到 '1' 表示叶子并跟一个字符,读到 '0' 则递归左右子树
int build_huffman_tree() {
    HuffmanNode node;
    node.left = node.right = -1;
    node.value = 0;
    node.leaf = false;
    int id = (int)huffman.size();
    huffman.push_back(node);

    if (tree_desc[pos] == '1') {
        pos++;
        huffman[id].leaf = true;
        huffman[id].value = tree_desc[pos++];
        return id;
    }

    pos++;
    huffman[id].left = build_huffman_tree();
    huffman[id].right = build_huffman_tree();
    return id;
}

// 把十六进制字符转换成数值 0..15
int hex_value(char ch) {
    if (ch >= '0' && ch <= '9') return ch - '0';
    if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
    return ch - 'A' + 10;
}

// 解码 Huffman 编码串:token 形如 'H' + 偶数个十六进制字符。
// 最后一个字节表示末尾补 0 的个数 p,其余字节高位在前,逐位从根节点走到叶子还原字符串
string decode_huffman_string(const string &token) {
    vector<int> bytes;
    for (int i = 1; i < (int)token.size(); i += 2) {
        bytes.push_back(hex_value(token[i]) * 16 + hex_value(token[i + 1]));
    }
    int padding = bytes.back();             // 最后一字节是补 0 个数
    bytes.pop_back();
    int total_bits = (int)bytes.size() * 8 - padding; // 去掉补 0 后的有效位数

    string result;
    int current = 0;                        // 当前所在节点,0 为根
    for (int bit = 0; bit < total_bits; bit++) {
        int byte_id = bit / 8;
        int offset = 7 - bit % 8;           // 每个字节高位在前
        int direction = (bytes[byte_id] >> offset) & 1; // 0 走左子树,1 走右子树
        if (direction == 0) current = huffman[current].left;
        else current = huffman[current].right;
        if (huffman[current].leaf) {        // 到叶子得到一个字符,回到根继续
            result.push_back(huffman[current].value);
            current = 0;
        }
    }
    return result;
}

// 解码一个字符串:非 'H' 开头原样输出;'HH' 开头去掉一个 H;其余按 Huffman 解码
string decode_string(const string &token) {
    if (token[0] != 'H') return token;
    if (token.size() >= 2 && token[1] == 'H') return token.substr(1);
    return decode_huffman_string(token);
}

// 根据全局编号取条目:编号不超过静态表大小取静态表,否则换算成动态表下标
TableEntry get_entry(int number) {
    if (number <= (int)static_table.size()) return static_table[number - 1];
    return dynamic_table[number - (int)static_table.size() - 1];
}

// 插入动态表:新条目放队首,达到容量上限时先淘汰最旧的队尾条目
void insert_dynamic_table(const TableEntry &entry) {
    if ((int)dynamic_table.size() == dynamic_limit) dynamic_table.pop_back();
    dynamic_table.push_front(entry);
}

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

    int static_size;
    cin >> static_size >> dynamic_limit;

    // 读入静态表原始字符串(其中可能包含 Huffman 编码)
    vector<pair<string, string> > raw_static(static_size);
    for (int i = 0; i < static_size; i++) {
        cin >> raw_static[i].first >> raw_static[i].second;
    }

    // 读入描述串并恢复 Huffman 树
    cin >> tree_desc;
    pos = 0;
    build_huffman_tree();

    // 解码静态表的每个条目
    static_table.resize(static_size);
    for (int i = 0; i < static_size; i++) {
        static_table[i].key = decode_string(raw_static[i].first);
        static_table[i].value = decode_string(raw_static[i].second);
    }

    // 按顺序处理每条指令
    int instruction_count;
    cin >> instruction_count;
    for (int instruction = 0; instruction < instruction_count; instruction++) {
        int operation, number;
        cin >> operation >> number;
        TableEntry entry;

        if (operation == 1) { // 表格引用:直接输出对应编号的条目
            entry = get_entry(number);
            cout << entry.key << ": " << entry.value << '\n';
            continue;
        }

        // 字面量指令:编号为 0 时字段名由输入给出,否则借用表格条目的字段名
        string raw_key;
        string raw_value;
        if (number == 0) {
            cin >> raw_key >> raw_value;
            entry.key = decode_string(raw_key);
        } else {
            cin >> raw_value;
            entry.key = get_entry(number).key;
        }
        entry.value = decode_string(raw_value);
        cout << entry.key << ": " << entry.value << '\n';

        // 字面量并索引:输出后插入动态表,供后续指令引用
        if (operation == 2) insert_dynamic_table(entry);
    }

    return 0;
}

复杂度

设所有输入字符串解码后的总长度为 LL,指令数为 NN

  • 时间:解码字符串共 O(L)O(L),每一步沿树走到叶子、编码长度不超过 8 位可看作常数;动态表插入为 O(1)O(1),总时间复杂度 O(N+L)O(N + L)
  • 空间:存储 Huffman 树、两张表与字符串,共 O(L+S+D)O(L + S + D)

总结

这是一道字符串协议模拟题,把编码树恢复、二进制串解码和有限动态表模拟组合在一起。按输入协议逐层拆开就能避免混淆;关键是要读清 Huffman 串的字节序与补零约定,以及全局编号到表格下标的换算。