备注:CCF CSP第三题 题目都很长,会引入其他你没学过的知识,不过给与的解释肯定能看懂,所以这里大家一定需要坚持住, 把题目全部看完,相信自己,我不勇敢谁替我坚强!
样例输入
2
1
2
样例输出
1.0.0.0/8
2.0.0.0/8
样例输入
2
10/9
10.128/9
样例输出
10.0.0.0/8
样例输入
2
0/1
128/1
样例输出
0.0.0.0/0
By the way: 这是 CCF CSP 的第三题 ,也是题目特别长,和实际结合特别密切的题目,笔者去年做这一题,虽然看懂了题目,还是感慨自己的编程水平不够,没有得分。这一次再认真阅读完其他人的博客信息,了解了如何解题,参照他人的方法,自己模仿着写出来了,借鉴了别人的代码和学习了他人一些编程的相关函数,学习了 list 双向链表的使用,也算是收货颇多吧。
1、关键就是 解决IP地址、IP前缀问题,我们使用一个结构体来储存,ip和len。题目已经告知输入的IP前缀 一定正确,不过IP地址存在三种情况:标准型、省略长度型、省略后缀型,还是就是为了合并或者排序,我们需要把IP变为2进制,也就是变为32位数,这样就可以使用一个普通的字符串符号比较就可以来判断大小了。
2、为了排序和合并方便,我们采用链表来储存,因为链表便于插入和删除,尤其是合并的删除,十分方便,采用STL中的list容器来储存,我们使用sort函数,重写cmp,将所有结构体储存之后,排序。
3、从小到大合并,我们使用了 Merge1()函数,一个j来遍历,一个i,先判断是否是匹配集,根据len和ip值来判断,这里特别说一下len(掩码), 它要求长度为len的数字必须相同。使用我们判断是否可以匹配,如果可以匹配则删除范围小的,然后j指向下一个,如果不能匹配,则i和j同时指向下一个。
4、同级合并,我们使用了 Merge2()函数,一个j来遍历,一个i,先判断是否为同级,如果为同级,则需要将j删除,然后i需要将len长度减1,同时看见 i之前是否还有元素,如果之前还有元素,需要回退一步,否则继续从i开始考虑(题目提供的解题思路已经给出)。
5、输出 需要 把二进制转换为 十进制进行输出,同时需要注意 输出格式 为标准型。
#include
#include
#include
#include
#include
#include
using namespace std;
struct IP {
string ip="";
int len = -1;
};
IP toip(string s) { //得到一个长度为32位的二进制编码字符串
IP ipadr;
string ipstr;
for (int i = 0; i <= s.size(); i++) { //等于 s.size()是为了 处理为数字仅有 1位的情况
if (i == s.size() || !isdigit(s[i])) {
int k = stoi(ipstr);//转化为 数字
int d = 128;
while (d > 0) { //转换为二进制数
if (k / d) {
ipadr.ip+= "1";
k -= d;
}
else {
ipadr.ip += "0";
}
d /= 2;
}
if (s[i] == '/') { //结束循环
ipadr.len = stoi(s.substr(i + 1));//得到ip前缀
break;
}
ipstr = ""; //归为空
}
else ipstr += s[i]; //数字
}
if (ipadr.len == -1) { //省略长度型
ipadr.len = ipadr.ip.size();
}
while(ipadr.ip.size() < 32) { //省略后缀型
ipadr.ip += "0";
}
return ipadr;
}
bool cmp(const IP &ip1,const IP & ip2) {
if (ip1.ip == ip2.ip) return ip1.len < ip2.len;
else return ip1.ip < ip2.ip;
}
bool isChild(IP &a, IP &b) {//判断b是否是a匹配集的子集
if (a.len > b.len) { //a的掩码比b长
return false;
}
for (int i = 0; i < a.len; i++) { //在掩码内看是否匹配
if (a.ip[i] != b.ip[i]) return false;
}
return true;
}
void merge1(list &l) {
auto i = l.begin(), j = l.begin(); //让l去下
++j;// j访问下一个
while (j != l.end()) {
if (isChild(*i, *j)) { //如果 j是 i的子集 则把j删除
j = l.erase(j); //返回被删除元素的下一个元素位置
}
else {
i++;
j++;
}
}
}
bool Issamelevel(IP &a, IP &b) {//判断a是否和b同级
if (a.len != b.len) {
return false;
}
for (int i = 0; i < a.len - 1; i++) { //前一位
if (a.ip[i] != b.ip[i]) return false;
}
return true;
}
void merge2(list &l) {
auto i = l.begin(), j = l.begin();
j++;
while (j != l.end()) {
if (Issamelevel(*i, *j)) {
j = l.erase(j);
(*i).len--;
if (i != l.begin()) {
i--;
j--;
}
}
else {
i++;
j++;
}
}
}
int main()
{
int n;
cin >> n;
list ipadr;
while (n--) {
string s;
cin >> s;
ipadr.push_back(toip(s));
}
ipadr.sort(cmp);
merge1(ipadr);
merge2(ipadr); //得出来 ip 为 32位
for (auto it = ipadr.begin(); it != ipadr.end();it++) {
for (int i = 0; i < 4; i++) { //三部分
int k = 0;
for (int j = 0; j < 8; j++) //8位二进制数转为十进制
{
k = k * 2 + ((*it).ip[j + 8 * i] - '0');
}
cout << k;
i < 3 ? cout << "." : cout << "/";
}
cout <<(*it).len << endl;
}
return 0;
}