洛谷 P2815 IPv6地址压缩 题解
P2815 IPv6地址压缩
题目背景
(友情提示:IPv6基础知识曾多次出现在NOIP初赛中)Internet Protocol,互联网协议,即为我们常说的IP。我们目前常说的IP主要指它的第四版,即IPv4,它由IETF于1981年发布。它的地址长度是32个二进制位,因此也就有2^32个IP地址可供使用,约为43亿,在当时,谁也没有料想到IPv4如此庞大的地址长度会有用完的一天。
在21世纪的今天,互联网的蓬勃发展早就了我们如今便利的生活。当下,世界人口已超过70亿,计算机和各种联网设备已经走入千家万户,而不再仅是上个世纪80年代科学家们的工具。此时便出现了人们日益增长的联网设备需要同落后IPv4地址长度之间的矛盾。尽管可以通过网络地址翻译能技术来共享IP地址,临时解决枯竭的问题,但显然不是长久之计。
IETF也有先见之明,早早地于1998年发布了IPv6协议,从微软2006年发布的Windows Vista开始成为默认安装的网络协议。作为IPv4的继任者,它的地址长度为128个二进制位,也就是2^128个IP地址可供使用。然而面对这冗长的地址,一位记忆力不好的网络工程师小明在配置路由表时遇到了许许多多多的困难,现在他找到了你,希望你帮忙编写一个程序来按照IPv6地址标准的格式压缩规则来压缩IPv6地址。
题目描述
IPv6格式
IPv6二进位制下为128位长度,以16位为一组,每组以冒号“:”隔开,可以分为8组,每组以4位十六进制方式表示。
例如:2001:0db8:0000:0000:0123:4567:89ab:cdef 是一个合法的IPv6地址。
同时IPv6地址在某些条件下可以压缩:
①每组数字代表的独立16进制数可以省略前位的0。
例如上面的IPv6地址可被压缩为:
2001:db8:0:0:123:4567:89ab:cdef
②可以用双冒号“::”表示一组0或多组连续的0,但只能出现一次
例如上面的IPv6地址可被压缩为:
2001:db8::123:4567:89ab:cdef
请你帮助记忆力不好的网络工程师小明解决他遇到的问题。
规则补充:
①输入数据为完全展开的IPv6地址,确保输入的IPv6地址不含双冒号,每组地址省略的0都会被补充上去。
②双冒号只能使用一次,因此我们压缩最长的全0组
比如:2001:0db8:0000:0000:1:0000:0000:0000
我们压缩为2001:db8:0:0:1::,而非2001:db8::1:0:0:0
③双冒号只能只用一次,因此我们在我们遇到地址中多个连续全0组长度相同时,我们压缩最前面的一个。
2001:0db8:0000:0000:ffff:0000:0000:1
压缩为2001:db8::ffff:0:0:1,而非2001:db8:0:0:ffff::1
④输入的IPv6地址可能无法被压缩,因此请照原样输出。
提示:本题所示的压缩规则与macOS(Darwin)默认的IPv6地址显示方式相同,而Windows和Linux只遇到一组全0时不会使用::进行压缩。但用此方法压缩过的IPv6地址一样可以被Windows和Linux正确识别。
例如:2001:0db8:ffff:0000:0123:4567:89ab:cdef
Darwin压缩为:2001:db8:ffff::123:4567:89ab:cdef
Linux、Windows压缩为:2001:db8:ffff:0:123:4567:89ab:cdef
输入格式
一串39个字符的字符串,代表一个完全展开的IPv6地址
输出格式
一串压缩后的IPv6地址
输入输出样例
输入 #1
240e:0014:6000:0000:0000:0000:0000:0001
输出 #1
240e:14:6000::1
输入 #2
2001:0db8:0000:0000:0000:0000:0000:0001
输出 #2
2001:db8::1
输入 #3
2001:4860:4860:0000:0000:0000:0000:8888
输出 #3
2001:4860:4860::8888
输入 #4
2400:8900:e000:0010:0000:0000:0000:0000
输出 #4
2400:8900:e000:10::
输入 #5
0000:0000:0000:0000:0000:0000:0000:0000
输出 #5
::
输入 #6
0000:0000:0000:0000:0000:0000:0000:0001
输出 #6
::1
输入 #7
2001:0db8:ffff:0000:0123:4567:89ab:cdef
输出 #7
2001:db8:ffff::123:4567:89ab:cdef
输入 #8
1234:5678:9abc:def0🔢5678:9abc:def0
输出 #8
1234:5678:9abc:def0🔢5678:9abc:def0
输入 #9
0001:0000:0000:0000:0000:0000:0000:0001
输出 #9
1::1
输入 #10
0000:0000:0000:0000:0000:0000:0001:0002
输出 #10
::1:2
【思路】
模拟
【题目大意】
将完整的的IPv6的显示方式压缩为macOS(Darwin)默认的IPv6地址显示方式
【题目分析】
压缩方式即为:
将每一组数 (每一组数是没有被:隔开的连续的4个数)的前导0去掉
不过如果是0000那就只能压缩为0
将最长的连续的0000这样的串可以压缩为::
比如0000:0000:0000:0000就可以压缩为::
如果有两个相同长度的那就替换前面的
【核心思路】
先找出最长的连续0000串第一个数的位置
到时输出输出完成::之后直接跳到这个串的最后一位就好了
总的来说上面这个还是比较好处理的
去除前导0才是最难的
这个时候就会有人说了,这不就是一个while循环搞的定的嘛,哪里难了
这个时候很容易会Wa掉第一个点
本人就出在这个问题上面
因为一般用while循环判断是不是这组数的最后一个一般会用下一个是不是:来判断
但是!
想没想过最后一组数的最后一位怎么判断?这个后面可没有:
很容易被忽略的哦
但是知道问题所在之后稍微特判一下就可以了
【提供一组小数据】
关于第一个点错误的样例
abcd:0000:0000🔡0012:0000:0001:0000
正解: abcd:🔡12:0:1:0
错解: abcd:🔡12:0:1:
【完整代码】
#include<iostream>
#include<cstdio>
#include<cstring>.
#include<string>
using namespace std;
string s;
int main()
{
cin >> s;
int l = s.length();
int a = 0;
int js = 0;//记录目前0串的长度
int M = 0;//记录最大的几组0的长度
int wz = -1;//最长0那一串中第一个0的位置
int last = -1;//上一个0串的位置
for(register int i = 0;i < l;i += 5)
{
if(s[i] == '0' && s[i + 1] == '0' && s[i + 2] == '0' && s[i + 3] == '0')//一组完整的0
{
if(last == i - 5)
{
last = i;
}
else
{
js = 0;
last = i;
a = i;
}
js ++;
}
if(js > M)
{
M = js;
wz = a;
}
}
int jj = 0;
while(jj < l)
{
if(jj == wz)
{
if(jj == 0)
cout << ":";
jj += M * 5 - 1;
if(jj == l)
cout << ":";
}
else
{
if(jj % 5 == 0)
{
if(jj >= 35)//如果是最后一组的话,那就不能根据下一个是不是:来判断这个0是不是这组数中最后一个数了
{
while(s[jj] == '0' && jj + 1 != l)
{
jj ++;
}
}
else
{
while(s[jj] == '0' && s[jj + 1] != ':')
{
jj ++;
}
}
}
cout << s[jj];
jj ++;
}
}
cout << endl;
return 0;
}
/*
2001:0db8:0000:0000:0123:4567:89ab:cdef
012345678901234567890123456789012345678
abcd:0000:0000:abcd:0012:0000:0001:0000
*/