写在前面
本题考查的是对STL::list
的熟练运用,以及对位运算的思考;思路在题目中给出的提示当中已经非常清晰。
解题思路
-
将输入转化为
list<前缀IP>
struct Prefix { vector<unsigned> ip; int len; }
这里要分两种特殊情况:省略后缀型和省略长度型;
我们使用的技术包括:字符串流stringstream、子串函数string::substr()、string转int函数stoi();
-
对链表进行排序
由于List的迭代器并不是随机访问迭代器,因此我们在这里不能够使用STL::sort(),而只能使用list::sort();
-
从小到大合并
考察了对链表的遍历操作,更重要的是判断b是否为a的子集。下面是思路:
-
将a和b的IP地址计算出来,它们分别是32位的比特。
-
如果
b.len > a.len
,那么b必不可能是a的子集。 -
分别取a和b比特流的前
a.len
位,若它们相等则b是a的子集具体的方法是计算一个掩码mask = 111111…00000,前面有a.len个1,后面有(32 - a.len)个0。
unsigned mask = (a.len == 0) ? 0 : ~((1 << (32 - a.len)) - 1);
-
-
同级合并
考察了对链表的遍历操作,更重要的是判断a’是否与a∪b相等。下面是思路:
-
进行合并的要求为
a.len == b.len
,我们假设a的IP地址的前缀是0010111
,即a.len == 7
。按照题目的方法,a_new.len == 6,a_new的前缀是
001011
。那么b要是多少才有可能让
a_new == a∪b
呢?很显然了,答案是0010110
。即a和b的前缀仅有最后一位是相反的。 -
用位运算表示为:
unsigned mask = 1 << (32 - a.len);
return (ip_b ^ mask) == ip_a;
-
满分代码(C++11,250ms)
// 开启O3优化
#pragma GCC optimize(3, "Ofast", "inline")
#include <cstring>
#include <iostream>
#include <list>
#include <sstream>
#include <vector>
using namespace std;
struct Prefix
{
vector<unsigned> ip;
int len;
// 构造函数:根据ip字符串解析ip前缀
Prefix(const string &str)
{
size_t pos = str.find('/');
if (pos != string::npos)
{
len = stoi(str.substr(pos + 1)); // len
string ip_str = str.substr(0, pos);
stringstream ss(ip_str);
unsigned num;
while (ss >> num) // 输入数字
{
ip.push_back(num);
ss.get(); // 输入数字后面的"."
}
}
else // 省略长度型
{
stringstream ss(str);
unsigned num;
while (ss >> num)
{
ip.push_back(num);
ss.get();
}
len = ip.size() * 8; // 计算len
}
// ip地址有4个数字,不足4个在后面补0
size_t left = 4 - ip.size();
for (size_t i = 0; i < left; ++i)
ip.push_back(0);
}
// 定义偏序,便于调用sort
friend bool operator<(const Prefix &a, const Prefix &b)
{
if (a.ip != b.ip)
return a.ip < b.ip;
return a.len < b.len;
}
// 根据vector<unsigned> ip 计算 ip地址
unsigned get_ip()
{
unsigned ip_num = 0, mul = 1;
for (int i = 3; i >= 0; --i)
{
ip_num += mul * ip[i];
mul *= 256;
}
return ip_num;
}
// b是否为a的子集
bool is_subset_of(Prefix &a)
{
if (len < a.len)
return false;
unsigned ip_a = a.get_ip();
unsigned ip_b = get_ip();
unsigned mask = (a.len == 0) ? 0 : ~((1 << (32 - a.len)) - 1);
return (mask & ip_a) == (mask & ip_b);
}
// 输出ip地址
void print()
{
cout << ip[0] << '.' << ip[1] << '.' << ip[2] << '.' << ip[3] << '/' << len << '\n';
}
};
// ip前缀列表
list<Prefix> pre_list;
// 第二步:按大小合并
void merge_by_size()
{
for (auto a = pre_list.begin(); a != pre_list.end();)
{
auto b = next(a);
if (b == pre_list.end()) // 如果a后面没有元素了,则可以退出
return;
if (b->is_subset_of(*a)) // 如果b是a的子集,则删除b(注意此时迭代器a不动)
pre_list.erase(b);
else
++a;
}
}
// 第三步核心问题:判断a_new是否可以代替a∪b
bool isMergable(Prefix &a, Prefix &b, Prefix &a_new)
{
unsigned ip_a = a.get_ip();
unsigned ip_b = b.get_ip();
unsigned mask = 1 << (32 - a.len);
return (ip_b ^ mask) == ip_a;
}
// 第三步:同级合并
void merge_with_same_rank()
{
for (auto a = pre_list.begin(); a != pre_list.end();)
{
auto b = next(a);
if (b == pre_list.end())
return;
Prefix a_new(*a);
if (--a_new.len < 0) // 如果a'的ip地址不合法,则移动至下一个元素
{
++a;
continue;
}
// 如果a.len == b.len:且可以合并
if (a->len == b->len && isMergable(*a, *b, a_new))
{
*a = a_new; // 将a替换为a'
pre_list.erase(b); // 删除b
if (a != pre_list.begin()) // 后退一位继续操作
--a;
}
else
++a;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
cin.get();
// 处理输入的ip字符串
string line;
while (n--)
{
getline(cin, line);
pre_list.push_back(Prefix(line));
}
// 三步处理
pre_list.sort();
merge_by_size();
merge_with_same_rank();
// 打印输出
for (Prefix &p : pre_list)
p.print();
return 0;
}
如果对您有帮助,记得点个赞哟 (^U^)ノ