Skip to content

Shorten IPv6 Address - 题解

标签与难度

标签: 字符串处理, 模拟, 进制转换, IPv6, 细节处理, 枚举 难度: 1600

题目大意喵~

主人,你好呀!这道题是关于网络世界的魔法咒语——IPv6地址的喵~!

我们拿到的是一个由 01 组成的、长达128位的二进制字符串。我们的任务呢,就是把它变成人类(和我!)更容易阅读的、最短的十六进制形式。

具体来说,有这么几条规则需要我们遵守,喵:

  1. 分组与转换:首先,要把128位的二进制串分成8个小段,每段16位。每一段都叫做一个“字段”(field)。然后,我们要把每个16位的二进制字段,都转换成一个4位的十六进制数。这些十六进制数之间用冒号 : 连接。
  2. 省略前导零:在每个十六进制字段里,前面的 0 都可以被丢掉。比如说,0123 就变成了 1230000 就变成了 0。但是 abcd 还是 abcd,不能乱丢哦!
  3. 零的压缩:如果有一连串(至少两个)的字段都是 0,我们可以用一个双冒号 :: 来代替它们和它们旁边的冒号。但是,一个地址里最多只能用一次 :: 魔法哦!
  4. 最终抉择:可能会有好几种缩短的方法,怎么选呢?
    • 首先,我们要选最终字符串长度最短的那一个。
    • 如果最短的有好几个,长度都一样,那我们就要选那个字典序最小的。字典序就是像查字典一样,从头开始一个一个字符比较大小,喵~

我们的目标就是根据这些规则,找到那个最完美、最简短的IPv6地址表示法!

解题思路分析

嘿嘿,这道题看起来规则好多,有点像在解一个精巧的谜题呢,喵~!不过别担心,只要我们一步一步来,就能把它梳理得清清楚楚。

最关键的地方在于那个 :: 压缩规则和最后的“最短+字典序最小”选择规则。如果我们试图想出一个非常聪明的、一步到位的公式来找到最佳压缩位置,可能会因为各种边界情况而头晕眼花。

所以,我推荐一个更稳妥、更直接的办法:把所有可能的压缩方式都试一遍,然后从中选出最好的那个! 这就像猫咪把所有可疑的纸箱都钻进去看看,总能找到最舒服的那个,喵~

下面是我们的探险步骤:

第一步:解析与转换,喵!

我们首先要把输入的128位二进制串,变成我们方便处理的8个十六进制字段。

  1. 切片:把128位的长字符串,切成8个16位的小字符串。
  2. 进制转换:对每个16位的小串,我们要把它从二进制转成十六进制。一个简单的方法是,先把16位二进制转成一个整数,然后再把这个整数格式化成十六进制字符串。比如在C++里,sprintf 或者 stringstream 都能轻松做到。
  3. 美化字段:转换后的十六进制字符串(比如 "00ab", "0000"),需要去掉前导零。我们可以写个小函数,找到第一个不是 '0' 的字符,从那里开始截取。要特别注意,如果一个字段本身就是 0(原为 "0000"),那它应该变成 "0" 而不是空字符串哦。

经过这一步,我们就得到了一个包含8个字符串的列表(vector<string>),例如 {"0", "0", "123", "4567", "89ab", "0", "0", "0"}。这是我们后续操作的基础。

第二步:寻找所有可压缩的零段,呐~

规则说,只有连续两个或以上的零字段才能被压缩。所以我们要在上一步得到的8个字段里,找出所有符合这个条件的“零段”。

我们可以遍历这8个字段:

  • 当遇到一个 "0" 时,就往后数,看看连续的 "0" 有多长。
  • 如果长度 L >= 2,我们就记录下这个零段的起始和结束位置。比如 (start_index, end_index)

对于 {"1", "0", "0", "2", "0", "0", "0", "3"},我们就能找到两个可压缩的零段:

  • 第一个:从索引 12(长度为2)。
  • 第二个:从索引 46(长度为3)。

第三步:生成所有候选方案并一决高下,的说!

现在我们有了所有可以施展 :: 魔法的地方。接下来就是生成所有可能的缩写形式,然后进行比较。

  1. 基础方案:首先,我们生成一个完全不使用 :: 的基础版本。就是简单地把8个字段用 : 连接起来。这个将作为我们比较的初始“擂主”。 "1:0:0:2:0:0:0:3"

  2. 压缩方案:对于上一步找到的每一个零段,我们都生成一个对应的压缩版本。

    • 压缩第一个零段 (12):
      • 取出零段前面的部分:"1"
      • 加上 ::
      • 取出零段后面的部分:"2:0:0:0:3"
      • 拼接起来得到:"1::2:0:0:0:3"
    • 压缩第二个零段 (46):
      • 取出零段前面的部分:"1:0:0:2"
      • 加上 ::
      • 取出零段后面的部分:"3"
      • 拼接起来得到:"1:0:0:2::3"
  3. 决斗!:每生成一个候选方案,就和我们当前的“擂主”(最佳方案)打一架。

    • 规则A(比长度):如果新方案比“擂主”更短,那它就毫无疑问地胜出,成为新的“擂主”。
    • 规则B(比字典序):如果新方案和“擂主”一样长,那就比较它们的字典序。如果新方案的字典序更小,它也能抢走“擂主”的位置。

把所有零段都试过一遍之后,留在擂台上的那个,就是我们最终的答案啦!这个方法虽然朴素,但它完美地遵循了题目的所有规则,绝对不会出错,喵~

代码实现

这是我根据上面的思路,精心编写的代码哦!希望能帮到你,喵~

cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <sstream>

// 一个辅助函数,用来将16位的二进制字符串转成美化后的十六进制字符串
// 例如 "0000000000000000" -> "0"
// "0000000100100011" -> "123"
std::string convert_field(const std::string& bin_field) {
    if (bin_field.length() != 16) {
        // 安全检查,虽然题目保证是16位
        return "";
    }
    
    // 从二进制字符串转成整数值
    int value = 0;
    for (int i = 0; i < 16; ++i) {
        if (bin_field[i] == '1') {
            value |= (1 << (15 - i));
        }
    }
    
    // 使用 stringstream 将整数转成小写十六进制字符串
    std::stringstream ss;
    ss << std::hex << value;
    return ss.str();
}

// 一个辅助函数,用来将字段列表拼接成一个完整的IPv6地址字符串
std::string join_fields(const std::vector<std::string>& fields, int start, int end) {
    std::string result = "";
    for (int i = start; i <= end; ++i) {
        result += fields[i];
        if (i < end) {
            result += ":";
        }
    }
    return result;
}

void solve() {
    std::string binary_ipv6;
    std::cin >> binary_ipv6;
    
    // --- 第一步:解析与转换 ---
    std::vector<std::string> fields;
    for (int i = 0; i < 8; ++i) {
        std::string bin_field = binary_ipv6.substr(i * 16, 16);
        fields.push_back(convert_field(bin_field));
    }
    
    // --- 第三步(前奏):生成基础方案,作为初始擂主 ---
    std::string best_representation = join_fields(fields, 0, 7);
    
    // --- 第二步:寻找所有可压缩的零段 ---
    std::vector<std::pair<int, int>> zero_blocks;
    for (int i = 0; i < 8; ++i) {
        if (fields[i] == "0") {
            int j = i;
            while (j < 8 && fields[j] == "0") {
                j++;
            }
            // 只有长度大于等于2的零段才算数
            if (j - i >= 2) {
                zero_blocks.push_back({i, j - 1});
            }
            i = j - 1; // 继续从零段后面开始找
        }
    }
    
    // --- 第三步(高潮):生成并比较所有压缩方案 ---
    for (const auto& block : zero_blocks) {
        int start_idx = block.first;
        int end_idx = block.second;
        
        std::string candidate_rep = "";
        
        // 构建压缩后的字符串
        if (start_idx == 0) {
            // 如果零段从头开始,例如 ::...
            candidate_rep = "::";
            candidate_rep += join_fields(fields, end_idx + 1, 7);
        } else if (end_idx == 7) {
            // 如果零段在末尾结束,例如 ...::
            candidate_rep = join_fields(fields, 0, start_idx - 1);
            candidate_rep += "::";
        } else {
            // 如果零段在中间,例如 ...::...
            candidate_rep = join_fields(fields, 0, start_idx - 1);
            candidate_rep += "::";
            candidate_rep += join_fields(fields, end_idx + 1, 7);
        }
        
        // 决斗!
        if (candidate_rep.length() < best_representation.length()) {
            best_representation = candidate_rep;
        } else if (candidate_rep.length() == best_representation.length() && candidate_rep < best_representation) {
            best_representation = candidate_rep;
        }
    }
    
    std::cout << best_representation << std::endl;
}

int main() {
    // 提高输入输出效率,喵~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    
    int t;
    std::cin >> t;
    for (int i = 1; i <= t; ++i) {
        std::cout << "Case #" << i << ": ";
        solve();
    }
    
    return 0;
}

复杂度分析

  • 时间复杂度: O(1)O(1) 因为输入的IPv6地址长度是固定的128位,所以我们处理的字段数(8个)、寻找零段的次数、生成的候选方案数都是一个很小的常数。对于每个测试用例,执行的操作数量是恒定的,所以时间复杂度是 O(1)O(1),喵~

  • 空间复杂度: O(1)O(1) 我们使用了 std::vector<std::string> 来存储8个字段,以及一些临时字符串来存放候选方案。因为字段数量和字符串长度都有一个很小的上限,所以占用的额外空间也是一个常数。因此,空间复杂度是 O(1)O(1)

知识点总结

这道题是很好的练习,可以锻炼我们细心处理问题的能力,喵~

  1. 字符串处理: 核心技能!包括字符串的截取 (substr)、拼接 (+)、查找和比较。
  2. 进制转换: 从二进制到十六进制的转换是基础。使用 stringstream 或者 sprintf 可以让代码更简洁。
  3. 模拟与枚举: 当问题的优化规则比较复杂时,直接模拟所有可能性并根据规则进行比较,是一种非常强大且不易出错的策略。这叫做“暴力美学”,喵哈哈!
  4. 细节是魔鬼: 要特别注意各种边界情况,比如零段在地址的开头 (::...)、结尾 (...::),以及全零的地址 (::)。我的代码里对这些情况都做了处理哦!

希望这篇题解能让你对这道题有更深的理解!编程就像一场有趣的冒险,只要有耐心和热情,任何难题都能被攻克,喵~!加油哦,主人!