这题看起来很可怕,考证的时候望而却步。过了好久,直到现在才做题。仔细研究下,发现原来不难,真的是心态爆炸了。
题目中的概念:(其实静下心来看,题目很好理解的。。可能是考试时心态崩了。。。)
ipv4地址:一共32位,分为4个十进制数表示。
ip前缀:<(ip地址) / (约掉IP地址(二进制表示)后面的0之后ip地址的长度,题目称前缀长度)>
ip前缀列表:一列ip前缀呗。
匹配:
得到ip前缀的前缀长度len
ip地址的前len长的地址 == ip前缀的前len长的地址 就是匹配(就是前面长得一样)
所以一个ip前缀可以匹配到1-n个的ip地址,比如 255.255.255.0 / 24 就可以匹配到 255.255.255.0-255.255.255.255之间的255(2^8)个ip地址。
等价:
两个前缀列表的匹配ip地址集合一样,就是说他们等价。
题目就是让我们找与输入的前缀列表等价的列长最小的前缀列表。
ip前缀的各种缩写,大家看题目就好了,应该不难。
难点:
处理缩写,处理合并。
这里只排序就有40分了。但是我只排序的代码拿了60分。
然后合并的方法,题目也教我们了,就差我们动手了。。。
细节:
从小到大合并:遍历列表,a与b合并,直接看b的前缀(按a的前缀长度算)是否和a的前缀一样;下标不用变,不能合并才下标加一。
同级合并:不同级就下标加一,同级不能合并下标加一,可以合并,看列表前面有没有ip前缀,有就下标减一,没有就不变。
python代码:
def minmax_merge():
"""从大到小合并"""
idx = 0
while idx < len(pref_sets) - 1:
first_pref, second_pref = pref_sets[idx], pref_sets[idx + 1]
first_bin, second_bin = ip2bin(first_pref[:-1]), ip2bin(second_pref[:-1])
minlen = first_pref[-1]
if first_bin[:minlen] == second_bin[:minlen]:
pref_sets.pop(idx + 1)
else:
idx += 1
def level_merge():
"""同级合并"""
idx = 0
while idx < len(pref_sets) - 1:
first_pref, second_pref = pref_sets[idx], pref_sets[idx + 1]
first_bin, second_bin = ip2bin(first_pref[:-1]), ip2bin(second_pref[:-1])
first_len, second_len = first_pref[-1], second_pref[-1]
if first_len == second_len:
l = first_len - 1
if first_bin[:l] == second_bin[:l]:
new_pref = bin2ip(first_bin[:l].ljust(32, '0')) + [l]
pref_sets.pop(idx)
pref_sets.pop(idx)
pref_sets.insert(idx, new_pref)
if idx - 1 >= 0:
idx -= 1
else:
idx += 1
else:
idx += 1
def ip2bin(ip):
"""十进制转二进制"""
binstr = ''
for e in ip:
binstr += (bin(e)[2:]).rjust(8, '0')
return binstr
def bin2ip(binstr):
"""二进制转十进制"""
ip = []
for i in range(0, len(binstr), 8):
ip.append(int(binstr[i:i + 8], 2))
return ip
n = int(input())
pref_sets = [] # 前缀列表
for i in range(n):
pref = input().split('/') # ip前缀。按斜杆分成ip地址和len
ip = [int(e) for e in pref[0].split('.')] # ip地址,按点分开
l = int(pref[1]) if len(pref) == 2 else len(ip) * 8 # 求len,如果前面的pref长度为2说明输入的前缀列表没有省略长度,有pref[1],直接取整;否则用ip地址的段数*8(题目有说明)
std_ip = [0] * 4 # 标准ip地址
for j, e in enumerate(ip):
std_ip[j] = e # 遍历ip,给ip地址赋值
std_pref = std_ip + [l] # 加上了len,变成标准ip前缀
pref_sets.append(std_pref) # 添加进前缀列表
pref_sets = sorted(pref_sets)
minmax_merge()
level_merge()
for p in pref_sets:
print('{}.{}.{}.{}/{}'.format(p[0], p[1], p[2], p[3], p[4]))
# 2
# 2
# 1
# 3
# 0/1
# 2/1
# 128/1
# 3
# 101.6.6.0/24
# 101.6.8.0/24
# 101.6.9.0/24
python代码(没合并的60分代码):
是不是很简单。。。没做真的亏了,十几行就可以拿60分了。
n = int(input())
pref_sets = [] # 前缀列表
for i in range(n):
pref = input().split('/') # ip前缀。按斜杆分成ip地址和len
ip = [int(e) for e in pref[0].split('.')] # ip地址,按点分开
l = int(pref[1]) if len(pref) == 2 else len(ip) * 8 # 求len,如果前面的pref长度为2说明输入的前缀列表没有省略长度,有pref[1],直接取整;否则用ip地址的段数*8(题目有说明)
std_ip = [0] * 4 # 标准ip地址
for j, e in enumerate(ip):
std_ip[j] = e # 遍历ip,给ip地址赋值
std_pref = std_ip + [l] # 加上了len,变成标准ip前缀
pref_sets.append(std_pref) # 添加进前缀列表
pref_sets = sorted(pref_sets)
for p in pref_sets:
print('{}.{}.{}.{}/{}'.format(p[0], p[1], p[2], p[3], p[4]))