CCF 201812-3 CIDR合并 注释非常详细

在这里插入图片描述
在这里插入图片描述
ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNzQ5MjYy,size_16,color_FFFFFF,t_70#pic_center)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
题记
这题用位运算可以很好的解决,但是注意一个坑:>>运算符的优先级比较低,如果不注意可能算的顺序跟你的思路不一致,所以没有把握就多加括号!!!多加括号!!!注释快能顶过代码行数了,就不赘述了。

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

const int  Maxn=1e5+10;
typedef long long LL;

//输入
int n;

struct point{
    //ip为ip地址的十进制表示 A B C D分别表示ip地址四个部分的十进制表示
    LL ip,A,B,C,D;
    //ip前缀的长度
    int len;
};

point IP[Maxn];

bool cmp(point a,point b){
    //ip地址为第一关键字,前缀长度为第二关键字
    if(a.ip==b.ip)
        return a.len<b.len;
    else
        return a.ip<b.ip;
}

//检查b的匹配集是否是a匹配集的子集
bool check(point a,point b){
    //a的前缀长度比b长,那么大一定不可能包含b(前缀越短集合越大)
    if(a.len>b.len)
        return false;
    else{
        //判断b的前缀是否包含a的前缀(前len长度)
        //前缀越长越具体范围越小集合越小
        if(a.ip>>(32-a.len)==b.ip>>(32-a.len))
            return 1;
        //它们前len长度不相等,就不是一个块的
        else
            return 0;
    }
}

//判断a与b是否同一级(前缀长度相等)并且是否能合并成更大的一块
bool check2(point a,point b){
    //前缀长度都不相等肯定不是同一级
    if(a.len!=b.len)
        return false;
    else{
        //如果a和b前len-1个前缀都相等并且第len为a为0,b为1那么a和b可以合并一大块(已经排过序了所以a是0)
        //一定要注意>>优先级很低,没有把握就加括号!!!
        if(((a.ip>>(32-a.len+1))==(b.ip>>(32-a.len+1)))&&((a.ip>>(32-a.len))%2==0)&&((b.ip>>(32-b.len))%2==1))
            return true;
        else
            return false;
    }
}

//返回比a前缀长度少一位的更大的块(相当于两个同级块合并成更大的一块)
point merge(point a){
    point ans;
    ans=a;
    ans.len--;
    return ans;
}

int main()
{
    cin>>n;
    string str;
    for(int i=0;i<n;i++){
        cin>>str;
        //num_point:点的个数 num_slash:斜杠个数(0/1)
        int num_point=0,num_slash=0;
        for(int j=0;j<str.size();j++){
            if(str[j]=='.')
                num_point++;
            else if(str[j]=='/')
                num_slash++;
        }
        //四个部分的值分别暂存到a,b,c,d
        LL a=0,b=0,c=0,d=0;
        //分别表示 遍历过的的点的个数 下一个点之前的部分的和 前缀长度
        LL cnt=0,sum_part=0,length=0;
        for(int j=0;j<str.size();j++){
            if(str[j]=='/'){
                cnt=-1;//后面就没有点了为了不进入下面无关的if语句从而错误更改abcd
                sum_part=0;//这里清零是为了算后面的前缀长度len
                continue;
            }
            else if(str[j]=='.'){
                cnt++;
                sum_part=0;//这里清零是计算下一个部分
                continue;
            }
            //如果不是标点执行下面的语句
            sum_part=sum_part*10+str[j]-'0';
            if(cnt==0) a=sum_part;
            else if(cnt==1) b=sum_part;
            else if(cnt==2) c=sum_part;
            else if(cnt==3) d=sum_part;
            if(j==str.size()-1)//把'/'后面的数字计算成十进制,这个就是前缀长度
                length=sum_part;
        }
        IP[i].A=a;IP[i].B=b;IP[i].C=c;IP[i].D=d;
        IP[i].ip=d+256*c+256*256*b+256*256*256*a;
        //计算前缀长度
        if(num_slash)
            IP[i].len=length;
        else
            IP[i].len=(cnt+1)*8;
    }

    vector<point> vec;
    //进行排序
    sort(IP,IP+n,cmp);
    //第一类合并(a可以涵盖b)
    for(int i=0;i<n;i++){
        int cnt=1;//表示i现在要"吃"的前缀是i下面的第cnt个
        //a一直往下"吃"直到吃不下
        while(check(IP[i],IP[i+cnt]))
            cnt++;
        //第i个吃掉下面比他小的后只剩下第i个
        vec.push_back(IP[i]);
        i+=cnt-1;//别忘了for循环会自动++
    }
    //第二类合并
    for(vector<point>::iterator it=vec.begin();it!=vec.end()-1;it++){
        if(check2((*it),(*(it+1)))){
            vec.erase(it+1);
            (*it)=merge((*it));
            //合并后要从i上面一个开始考虑,看看能不能合并出来更大的前缀
            if(it!=vec.begin())
                it-=2;//别忘了for循环有一个++
            else
                it--;
        }
    }
    //输出
    for(int i=0;i<vec.size();i++)
        cout<<vec[i].A<<"."<<vec[i].B<<"."<<vec[i].C<<"."<<vec[i].D<<"/"<<vec[i].len<<endl;
    return 0;
}

样例输入1

2
1
22

样例输入2

2
10/9
10.128/9

样例输入3

2
0/1
128/1
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值