CIDR合并
难是真的不难但是也写了我几个小时服了
这道题在有计网的基础上就很好理解了,没有在格式上有任何刁难你的。这里不讲背景了
官网提交结果以及满分代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<string, int> PSI;
const int N = 1e5 + 10;
int n;
struct node{
string ip;
int len;
ll ten; //ip前缀的十进制表示:为了方便排序
};
vector<node> ipPool;
vector<node> merge1; //第一轮合并的结果
vector<node> res; //同级合并的最终结果
int to_int(string str)
{
stringstream ssin(str);
int x;
ssin >> x;
return x;
}
string to_str(int m)
{
stringstream ssin;
ssin << m;
string str;
ssin >> str;
return str;
}
//用于第一步排序
bool cmp(node x, node y)
{
if(x.ten == y.ten) return x.len < y.len;
else return x.ten < y.ten;
}
//处理一个输入的ip,将其处理后存储到ipPool中
void handle(string str)
{
int k = str.find('/');
int len = 0; node temp;
if(k != -1) { len = to_int(str.substr(k + 1)); str = str.substr(0, k); }
//开始解析ip, 将字符串按照'.'分割开来
vector<string> vec;
for(int i = 0, j = 0; i < str.size(); i = j + 1){ //将str用'+'分割开来
j = str.find('.', i); //从下标i开始找+号
if(j == -1) j = str.size();
vec.push_back(str.substr(i, j - i));
}
if(!len) len = vec.size() * 8; //省略了长度型的ip书写方式
if(vec.size() != 4){
int sz = vec.size();
for(int i = 1;i <= 4 - sz;i ++){
vec.push_back("0"); //补0
}
}
string ip = ""; ll ten = 0; //将ip转化为十进制的数字
for(int i = 0;i < vec.size();i ++) {
ip = ip + vec[i] + '.';
ll x = to_int(vec[i]);
x <<= ((3 - i) * 8);
ten += x;
}
ip = ip.substr(0, ip.size() - 1); //删去最后一个点
ip += "/" + to_str(len);
temp.ip = ip; temp.len = len; temp.ten = ten;
ipPool.push_back(temp);
}
//判断ipPool中b位置ip的匹配集是否是a位置ip匹配集的子集
bool subset(int a, int b)
{
int lena = ipPool[a].len, lenb = ipPool[b].len;
if(lena > lenb) return false;
//接下来是lena <= lenb的情况,也就是二者的前lena个bit位置必须相同
int delta = 32 - lena;
int tena = ipPool[a].ten >> delta, tenb = ipPool[b].ten >> delta; //只保留前lena位
int temp = tena ^ tenb;
if(temp) return false; //前lena个bit位不相同
return true;
}
//将ipPool中的元素从小到大进行合并
void small_Large_Merge()
{
int a = 0, b = 1;
bool st[N]; //考虑某个位置的元素是否存在
memset(st, true, sizeof st);
while(b < n){ //n也是ipPool的大小
if(subset(a, b)){ st[b] = false; b ++; }
else{ a = b; b ++; }
}
for(int i = 0;i < n;i ++){
if(st[i]) merge1.push_back(ipPool[i]);
}
}
//对merge1进行同级合并
void final_Merge()
{
int a = 0, b = 1, sz = merge1.size();
bool st[N];
memset(st, true, sizeof st); //功能同前面相同
while(b < sz){
node t; //存储中间结果
int lena = merge1[a].len, lenb = merge1[b].len;
bool flag = false; //是否能合并
if(lena == lenb){
//看二者是否能合并, 也就是看前len - 1位置是否完全相同,且最后一位不同
int delta = 32 - lena;
int tena = merge1[a].ten >> delta, tenb = merge1[b].ten >> delta;
int tena2 = merge1[a].ten >> (delta + 1), tenb2 = merge1[b].ten >> (delta + 1);
int cnt = 0; //用来记录前len位有几位不相同
int temp = tena ^ tenb;
while(temp){
cnt ++;
temp &= (temp - 1);
}
temp = tena2 ^ tenb2;
if(!temp && cnt == 1){ //前len - 1相同,第len不同,可以合并
flag = true;
t.ten = merge1[a].ten;
t.len = merge1[a].len - 1; //长度不同
string ip = merge1[a].ip;
int k = ip.find('/');
ip = ip.substr(0, k + 1);
ip += to_str(t.len);
t.ip = ip;
}
}
if(flag){
merge1[a] = t; st[b] = false; //更新; a向前回溯
if(a > 0){ //a > 0才有回溯的必要,注意这里st[0]是不可能为false的
b = a; a --;
while(!st[a]) a --;
}
else { //无需向前找了
while(b < sz && !st[b]) b ++;
};
}
else {
a = b;
b ++;
while(b < sz && !st[b]) b ++;
}
}
for(int i = 0;i < sz;i ++){
if(st[i]) res.push_back(merge1[i]);
}
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0);cout.tie(0);
cin >> n;
for(int i = 0;i < n;i ++){
string ip; cin >> ip;
handle(ip);
}
sort(ipPool.begin(),ipPool.end(), cmp);
//现在开始从小到大合并
small_Large_Merge();
//同级合并
final_Merge();
for(auto x : res){
cout << x.ip << endl;
}
return 0;
}
本题我用了位运算来实现。能否合并的关键就是看前几位是否是相同的,如果前x位相同,第x + 1的异或结果为1的话,此时两个就可以合并成一个。因此我设置了一个len
来保存ip地址的十进制数。
但就是这个我改了很久的错误
第62行我的初始代码是:ten += ( x << ((3 - i) * 8) )
,结果总是负数
后来又测试了几个如下:
其实就是符号的问题。
如果我直接对128移位置,只在低位补零;此时的x
的表示形式就是10000000 0000000 0000000 0000000
。从补码的角度来说,因为最高位为1,此时就是负数!!!
而我若先赋值为128,此时这个数据只是存在与x
空间的存储形式为00000000 00000000 00000000 10000000
,再移位;此时变为00000000 10000000 0000000 0000000 0000000
最高位仍然为0