201812-3 CIDR合并
时间限制:1.0s
内存限制:512MB
题目描述:
分析
根据提示,我们需要
- 将ip前缀按照标准型表示后,对其按照ip大小、len大小排序
- 进行从小到大合并
- 进行同级合并
解法
-
选择合适的数据类型表示ip前缀,为了便于排序和合并,使用string数据类型表示ip的二进制串格式。此外使用list用来排序合并,因为有很多插入删除操作,list相较于vector更快(vector最后一个点超时)
struct IP { string ip; int len; bool operator <(const IP& a)const { return ip == a.ip ? (len < a.len) : (ip < a.ip); } }; list<IP> l;
-
将三种格式的前缀读入并转换成标准型
三种格式如下:
- 12.13.14.15/12
- 101/8
- 101.6
可以得到如下规律:
- 若读入的字符串没有'/',则说明是第三种,其len为('.'的个数+1)*8,否则:
- 若读入的字符串没有到三个'.',则说明是第二种,否则:
- 是标准型的第一种
可以通过逐个字符读入的方法,来进行判断存取。每遇到一个'.'或者'/',则将前面的数字值转换成二进制串存入;当没有满3个'.',则用0补全至三个'.',代码如下:
//将十进制数值转换成二进制串 string itos(int val) { string tmp = "00000000";//既避免补0也避免完成后reverse int i; for (i = 7; i >= 0 && val; i--) { tmp[i] = val % 2; val /= 2; } return tmp; } void read() { char c; string tip; int val = 0, k = 0, flag = 0; while ((c = getchar()) != '\n') { if (c == '.') { tip += itos(val); val = 0; k++;//记录'.'的个数 } else if (c == '/') { tip += itos(val); val = 0; flag = 1;//记录是否有'/' } else { val *= 10; val += c - '0'; } } int len = flag ? val : (k + 1) * 8; if (!flag) tip += itos(val);//注意如果没有'/'则要将最后的val转换成二进制串存入 int i; for (i = k; i < 3; i++) { tip += "00000000"; } aip.ip = tip; aip.len = len; l.push_back(aip); }
-
排序
//错误,list不能用algorithm里的sort bool cmp(IP a, IP b) { if (a.ip == b.ip) return a.len < b.len; return a.ip < b.ip; } sort(l.begin(), l.end(), cmp); //正确 l.sort();
-
从小到大合并
规则:从begin开始顺序选择a,b元素,若两者可以合并,则一定是b合并成a,去除b,再比较a和b下一个元素c;若两者不能合并,则比较b和c。
解释:从小到大合并一定是长len2向短len1合并,并且两者前len1位数字相同。根据排序规则我们知道:
- 如果a.ip == b.ip,则a.len <= b.len,那么a的ip集合一定包含b,可以去除b;
- 如果a.ip < b.ip,两者len未知,但是若a.len >= b.len则一定不能合并(b.ip > a.ip,b前b.len位一定有一个1对应a此处是0)
- 如果a.ip < b.ip 且 a.len < b.len,则只有两者前a.len位相同才能合并。
综上,只要a.ip和b.ip前a.len位相同,则可以合并到a,代码如下:
void merge1() { int i; for (it1 = l.begin(); it1 != l.end();)//注意在合并的情况下是不需要it1++的 { it2 = it1; it2++;//通过指向再自增的方式让it2指向下一个元素 if (it2 == l.end())//注意it2不要出界 break; for (i = 0; i < it1->len; i++) { if (it1->ip[i] != it2->ip[i]) break; } if (i == it1->len) l.erase(it2); else it1++; } }
-
同级合并
规则:若a.len == b.len,且a' = a.ip / len-1为a和b的并集,则去除a,b,加入a',此时若a'前面还有元素,则从该元素开始合并。
解释:对于两个len相同的同级ip,想要合并一定是第len位一个为0一个为1,这样他们合并之后的ip就是第len位为0的那个的ip(即ip小的,a),len为之前的len-1。例如:len-1和len位分别为00,01,10,11的四种情况,12可以合并为00/len-1,34可以合并为10/len-1。代码如下:
void merge2() { int i, len; for (it1 = l.begin(); it1 != l.end(); ) { it2 = it1; it2++; if (it2 == l.end()) break; len = it1->len; if (len == it2->len) { for (i = 0; i < len - 1; i++) { if (it1->ip[i] != it2->ip[i]) break; } if (i == len - 1) { l.erase(it2); (it1->len)--; if (it1 != l.begin()) it1--; } else it1++; } else it1++; } }
-
输出
每8个二进制数转换成十进制后输出,记得加'.'和'/'。
for (it1 = l.begin(); it1 != l.end(); it1++) { for (int i = 0; i < 32; i += 8) { val = 0; for (int j = i; j < i + 8; j++) { val = val * 2 + it1->ip[j] - '0'; } printf("%d", val); if (i != 24) putchar('.'); } printf("/%d\n", it1->len); }