这是一个很有意思的题目,思路和方法可以非常多,做为一个5分的填空题实在可惜,目前看了一圈网上的解答,很少有完整解答的,这里给一条思路和代码,仅供参考。
【问题描述】
小蓝最近在学习网络工程相关的知识。他最近学习到,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 的范围内),在提交答案时只填写这个整数,填写多余的
内容将无法得分。
第一个关键点在于理解Ipv6压缩的思想:【可以将最长的一段“连续的全 0 分组”用 :: 压缩一次】
例如把 0000:0000:00ab:0000:000a:0001:0a00:0e00 压成 :0️1:a00:e00 等。
注意:
不压缩时的长度 = 所有分组去掉前导零后的长度之和 + 7( 7 个冒号)
如果有一段最大长度为 r ( ≥ 2 ) r(\ge2) r(≥2) 的“连续零分组”,那么使用 : : :: :: 可以把那 r r r 个零分组从 r ∗ ‘ ‘ 0 " + ( r − 1 ) ∗ ‘ ‘ : " r * ``0" + (r-1) * ``:" r∗‘‘0"+(r−1)∗‘‘:" 替换成 ‘ ‘ : " ``:" ‘‘:"。
第二个关键点在于,可以把每个 16 位分组(0–65535)在去除前导零后,可能的“表示长度”及其出现次数统计出来:
分组值为 0:只会表示成 “0”,长度 =1。这种情况共有 1 种取值。
分组值非零时,根据最高有效的十六进制位在哪儿,可分为:
一条 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 → p o s + 1 pos \rightarrow pos+1 pos→pos+1 时,需要考虑这段可能是:
当 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 是全局最长连续零分组长度。
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);
}
}
983499503
ATFWUS 2025-04-14