当前位置: 首页 > news >正文

【2025蓝桥杯省赛填空压轴题-pythonA组和研究生组】Ipv6 解析(四维dp)

这是一个很有意思的题目,思路和方法可以非常多,做为一个5分的填空题实在可惜,目前看了一圈网上的解答,很少有完整解答的,这里给一条思路和代码,仅供参考。


文章目录

  • A.题目
  • B.思路
    • 1.压缩
    • 2.可能长度
    • 3.四维dp
    • 4.结果统计
  • C.代码
  • D.答案


A.题目

【问题描述】
小蓝最近在学习网络工程相关的知识。他最近学习到,IPv6 地址本质上是
一个 128 位的二进制数,而字符串形式的 IPv6 地址是由被冒号分开的八段 16
进制数组成的, 例如,下面每行是一个字符串形式的 IPv6 地址:
0000:0000:0000:0000:0000:0000:0000:0000
0000:0001:0000:0000:0000:0001:0000:0000
0000:0001:00ab:0000:0023:0000:0a00:0e00
0000:0000:00ab:0000:000a:0001:0a00:0e00
0000:0000:00ab:0000:0000:0001:0a00:0e00
其中,每一段最长 4 位,且每一段的前导零都可以去掉(如果 4 位都为 0
需要写成 0)。
另外,IPv6 地址还可以将其中相邻的值为 0 的段合并压缩起来,用两个冒
号来表示,不过只能压缩一段。
例如上述地址最短的压缩后的形式分别为
::
0:1::1:0:0
0:1🆎:23:0:a00:e00
:🆎0🅰️1:a00:e00
0:0🆎:1:a00:e00
小蓝想知道, 所有 IPv6 地址的最短压缩形式的长度的和为多少?由于答案
很大(甚至超过了 128 位二进制整数的范围),请填写答案时填写这个总和除以
109 + 7 的余数。

【答案提交】
这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个
整数(在 0 到 109 + 6 的范围内),在提交答案时只填写这个整数,填写多余的
内容将无法得分。

B.思路

1.压缩

第一个关键点在于理解Ipv6压缩的思想:【可以将最长的一段“连续的全 0 分组”用 :: 压缩一次
例如把 0000:0000:00ab:0000:000a:0001:0a00:0e00 压成 :🆎0🅰️1:a00:e00 等。
注意:

  • 如果没有长度 ≥2 的连续全零分组,则不值得使用 ::(对单个 0 分组用 :: 反而会变长)。
  • 若存在多段长度相同的连续零分组,都能达到相同的压缩长度,随便选哪一段做压缩都不会影响最终的最短表示长度

不压缩时的长度 = 所有分组去掉前导零后的长度之和 + 7( 7 个冒号)
如果有一段最大长度为 r ( ≥ 2 ) r(\ge2) r(2) 的“连续零分组”,那么使用 : : :: :: 可以把那 r r r 个零分组从 r ∗ ‘ ‘ 0 " + ( r − 1 ) ∗ ‘ ‘ : " r * ``0" + (r-1) * ``:" r‘‘0"+(r1)‘‘:" 替换成 ‘ ‘ : " ``:" ‘‘:"

  • 替换前此部分占 r + ( r − 1 ) = 2 r − 1 r + (r - 1) = 2r - 1 r+(r1)=2r1 个字符,替换后只剩下 : : :: :: 两个字符,故可少占 ( 2 r − 1 ) − 2 = 2 r − 3 (2r - 1) - 2 = 2r - 3 (2r1)2=2r3 个字符
  • 若最大连续零分组长度 < 2 < 2 <2,则不压缩。

2.可能长度

第二个关键点在于,可以把每个 16 位分组(0–65535)在去除前导零后,可能的“表示长度”及其出现次数统计出来:
分组值为 0:只会表示成 “0”,长度 =1。这种情况共有 1 种取值。
分组值非零时,根据最高有效的十六进制位在哪儿,可分为:

  • 长度 1 (十六进制 1 位):有 15 种取值 (0x1 ~ 0xf)
  • 长度 2:有 240 种取值 (0x10 ~ 0xff)
  • 长度 3:有 3840 种取值 (0x100 ~ 0xfff)
  • 长度 4:有 61440 种取值 (0x1000 ~ 0xffff)

3.四维dp

一条 IPv6 地址只有 8 段,我们可以在“分组层面”做一个多维 DP。
核心思想是:逐段扫描 8 个分组,用 DP 记录“目前已经处理到第几段、当前连续零分组长度、已出现的最大连续零分组长度、当前总的字符和”下,有多少种地址取值组合”。

我们令 dp [ p o s ] [ c u r R u n ] [ m a x R u n ] [ s u m L e n ] \text{dp}[pos][curRun][maxRun][sumLen] dp[pos][curRun][maxRun][sumLen],表示:当已经处理到第 p o s pos pos 段(取值范围 0~8),当前连续零分组长度是 c u r R u n curRun curRun,历史上最大的连续零分组长度是 m a x R u n maxRun maxRun,并且已经累加的“去前导零后字符总和”为 s u m L e n sumLen sumLen 时,有多少种具体的分组取值序列能到达这里。

四个维度的取值范围:

  • p o s pos pos 范围是 0~8(共 9 个状态,用于表示“还没开始到处理完第 8 段”)
  • c u r R u n curRun curRun 范围是 0~8(因为一口气最多可能 8 个分组都为 0)
  • m a x R u n maxRun maxRun 范围也是 0~8
  • s u m L e n sumLen sumLen 最大不会超过 8 × 4 = 32 8 \times 4 = 32 8×4=32(如果全部分组都是 4 位表示),最小是 8 8 8(如果全部分组都是 0,表示长度都为 1),所以可以开到 0~32 即可

状态转移:

在处理下一段 p o s → p o s + 1 pos \rightarrow pos+1 pospos+1 时,需要考虑这段可能是:

  • 0,表示长度 =1,有 1 种取值
    • 则新状态里 n e x t C u r R u n = c u r R u n + 1 nextCurRun = curRun + 1 nextCurRun=curRun+1
    • n e x t M a x R u n = m a x ( m a x R u n , n e x t C u r R u n ) nextMaxRun = max(maxRun, nextCurRun) nextMaxRun=max(maxRun,nextCurRun)
    • n e x t S u m L e n = s u m L e n + 1 nextSumLen = sumLen + 1 nextSumLen=sumLen+1
  • 非零分组,表示长度可能是 1、2、3、4;相应取值种数分别是 15、240、3840、61440
    • 则新状态里 n e x t C u r R u n = 0 nextCurRun = 0 nextCurRun=0 (因为连续零被断开了)
      n e x t M a x R u n = m a x R u n nextMaxRun = maxRun nextMaxRun=maxRun (不变)
      n e x t S u m L e n = s u m L e n + L nextSumLen = sumLen + L nextSumLen=sumLen+L (L 为该分组的表示长度 1/2/3/4)
      • 每种转移要把路径数 dp [ p o s ] [ c u r R u n ] [ m a x R u n ] [ s u m L e n ] \text{dp}[pos][curRun][maxRun][sumLen] dp[pos][curRun][maxRun][sumLen] 乘以“这一段对应的取值种数”再累加到下一状态去。

4.结果统计

p o s = 8 pos = 8 pos=8 时,说明 8 段都处理完了, s u m L e n sumLen sumLen 就是这 8 段去掉前导零后的总长度, m a x R u n maxRun maxRun 是全局最长连续零分组长度。

  • 对每个末状态 dp [ 8 ] [ c u r R u n ] [ m a x R u n ] [ s u m L e n ] \text{dp}[8][curRun][maxRun][sumLen] dp[8][curRun][maxRun][sumLen],其最短表示长度就是
    (sumLen) + 7    −    max ⁡ ( 0 ,    2 × ( maxRun ) − 3 ) \text{(sumLen)} + 7 \;-\; \max\bigl(0,\;2\times(\text{maxRun}) - 3\bigr) (sumLen)+7max(0,2×(maxRun)3)
  • m a x R u n < 2 maxRun < 2 maxRun<2,则减数为 0 (不使用 : : :: ::)
  • m a x R u n ≥ 2 maxRun \ge 2 maxRun2,则减数为 ( 2   ×   maxRun − 3 ) (2\,\times\,\text{maxRun} - 3) (2×maxRun3)
  • 把这一“最短表示长度”乘上末状态对应的路径数量(也就是有多少具体 IPv6 地址对应到这个状态),然后再对所有末状态求和,即可得到所有 IPv6 地址的最短表示长度之和。最后取模 ( 1 0 9 + 7 10^9+7 109+7) 输出。

C.代码

import java.util.Arrays;

public class Main {

    static final int MOD = (int)1e9 + 7;

    public static void main(String[] args) {
        // 按照表示长度 1/2/3/4 分别是多少种取值
        int[] nonZeroLen = {1, 2, 3, 4};
        // 长度=1(非零): 15 种; 长度=2: 240; 长度=3: 3840; 长度=4: 61440
        int[] nonZeroWays = {15, 240, 3840, 61440};
        
        /**
         * dp[pos][curRun][maxRun][sumLen]:表示前 pos 段处理完后,连续零分组数=curRun,
         * 历史最大连续零分组数=maxRun,已累计分组字符和=sumLen 的情况下,共有多少种 IPv6 地址
         * 把第4维sumLen合并到三维数组里,减少一维
         */
        long[][][] dpCur = new long[9][9][33];
        long[][][] dpNxt = new long[9][9][33];
        
        dpCur[0][0][0] = 1;

        // 逐段处理
        for (int pos = 0; pos < 8; pos++) {
            // 清空下一层
            for (int curRun = 0; curRun <= 8; curRun++) {
                for(int k = 0; k <= 8; k++) {
                    Arrays.fill(dpNxt[curRun][k], 0);
                }
            }

            // 枚举状态
            for (int curRun = 0; curRun <= 8; curRun++) {
                for (int maxRun = 0; maxRun <= 8; maxRun++) {
                    for (int sumLen = 0; sumLen <= 32; sumLen++) {
                        long count = dpCur[curRun][maxRun][sumLen];
                        if (count == 0) {
                            continue;
                        }

                        // 1) 零分组 => 表示长度=1
                        int nextCurRun0 = curRun + 1; // 连续零分组 + 1
                        int nextMaxRun0 = Math.max(maxRun, nextCurRun0);
                        int nextSumLen0 = sumLen + 1;
                        if (nextSumLen0 <= 32) {
                            dpNxt[nextCurRun0][nextMaxRun0][nextSumLen0] =
                                    (dpNxt[nextCurRun0][nextMaxRun0][nextSumLen0] + count) % MOD;
                        }

                        // 2) 非零分组 => 可能长度=1/2/3/4
                        for (int i = 0; i < nonZeroLen.length; i++) {
                            int L = nonZeroLen[i];
                            int waysThis = nonZeroWays[i];
                            int nextCurRun = 0;         // 连续零断掉
                            int nextSumLen = sumLen + L;
                            if (nextSumLen <= 32) {
                                long ways = (count * waysThis) % MOD;
                                dpNxt[nextCurRun][maxRun][nextSumLen] =
                                        (dpNxt[nextCurRun][maxRun][nextSumLen] + ways) % MOD;
                            }
                        }
                    }
                }
            }
            // dpNxt -> dpCur
            long[][][] tmp = dpCur;
            dpCur = dpNxt;
            dpNxt = tmp;
        }

        // 处理完 8 段后,dpCur[curRun][maxRun][sumLen] 就是所有地址在“分组层面”统计的结果
        // 最后把最短表示长度 累加到答案
        long answer = 0;
        for (int curRun = 0; curRun <= 8; curRun++) {
            for (int maxRun = 0; maxRun <= 8; maxRun++) {
                for (int sumLen = 0; sumLen <= 32; sumLen++) {
                    long count = dpCur[curRun][maxRun][sumLen];
                    if (count == 0) {
                        continue;
                    }
                    // 不压缩长度 = sumLen + 7
                    // 压缩贡献 = 如果 maxRun >= 2,则可减 (2*maxRun - 3)
                    int reduce = 0;
                    if (maxRun >= 2) {
                        reduce = 2 * maxRun - 3;
                    }
                    int minLen = (sumLen + 7) - reduce;

                    // 贡献 = minLen * count
                    long contrib = (long)minLen * count;
                    answer = (answer + (contrib % MOD)) % MOD;
                }
            }
        }
        // 983499503
        System.out.println(answer);
    }
}

D.答案

983499503


ATFWUS 2025-04-14

相关文章:

  • MySQL存储引擎:存储什么意思?引擎什么意思?存储引擎是什么?在MySQL中有什么作用?
  • 【CHNS】随访时间 整理
  • dnf install openssl失败的原因和解决办法
  • 第七届浙江省大学生网络与信息安全竞赛决赛Unserialize深度解析 1.0
  • 设计模式-观察者模式
  • warning C4828: 文件包含在偏移 0x194 处开始的字符,该字符在当前源字符集中无效(代码页 65001)
  • pyqt环境配置
  • hevc编码芯片学习-VLSI实现
  • aes密钥如何生成固定的16位呢?
  • 大表查询的优化方案
  • 【ComfyUI】蓝耘元生代 | ComfyUI深度解析:高性能AI绘画工作流实践
  • 详细介绍7大排序算法
  • Nginx用途以及好处:
  • Oracle数据库数据编程SQL<9.2 数据库逻辑备份和迁移Data Pump (EXPDP/IMPDP) 导出、导入>
  • 大型语言模型中中医知识的多模态基准数据集
  • HarmonyOS:使用Refresh组件实现页面下拉刷新
  • css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
  • Python Cookbook-6.2 定义常量
  • 数据结构考研复习
  • python蓝桥杯备赛常用算法模板
  • 马上评丨一些影视剧的片名,越来越让人看不懂
  • 神舟二十号3名航天员顺利进驻中国空间站
  • 消费者买国外电话卡使用时无信号,店铺:运营商故障,较少见
  • 上海楼市明显复苏:一季度房地产开发投资增长5.1%,土地市场重燃战火
  • 全球最大车展在上海启幕,解放日报头版头条:“看懂上海车展,就能预判未来”
  • 印控克什米尔地区发生针对游客枪击事件,造成至少25人丧生