CCF201812-3,CIDR合并

嗯,折腾了一上午,终于过了,这个题说难也难,说简单也简单,如果看着分数调bug满分还是挺容易的,但是考试时候一次性通过的可能就比较低了。。。

要写出来主要是要意识到两点,一个是从小到大合并的时候,要比较的是小的那个ip的前缀长度范围内两个ip是否相等,如果前缀长度范围内相等,那么另外一个一定是子集;二是同级合并的时候,存在递归合并的可能性,比如先一个23,然后两个24,两个24合并之后会生成一个23,他们是可以合并的,因此在实现的时候需要在合并的时候把迭代器向前退一个元素

再然后这个题用的人用字符串表示,我觉得读出来数字之后构造bitset更适合一些,各种操作也都很顺手。

using namespace std;
#include
#include
#include
struct ip{
    int a,b,c,d;
    bitset<32> uip;
    int arr[5];
    int suf;
    ip(int ia,int ib=-1,int ic=-1,int id=-1,int isuf=-1){
        int zero_count = 0;
        if(isuf==-1){
            if(ib==-1)zero_count++;
            if(ic==-1)zero_count++;
            if(id==-1)zero_count++;
            isuf = 32-zero_count*8;
        }
        suf = isuf;

        a = ia;
        if(ib == -1)ib=0;
        if(ic == -1)ic=0;
        if(id == -1)id=0;
        b=ib;c=ic;d=id;

        unsigned long ui = 0;
        ui+=a;
        ui<<=8;
        ui+=b;
        ui<<=8;
        ui+=c;
        ui<<=8;
        ui+=d;
        uip = ui;

        arr[0] = ia;
        arr[1] = ib;
        arr[2] = ic;
        arr[3] = id;
        arr[4] = suf;

    }
    void show(){
        printf("%d.%d.%d.%d/%d\n",a,b,c,d,suf);
    }
};


ip create(){
    int iparr[] = {0,-1,-1,-1,-1};
    char split;
    int index = 0;
    while(index<5){
        scanf("%d",&iparr[index]);//每次读一个数
        split=getchar();
        if(split=='.'){//根据数之后接的符号判断是否结束
            index++;
            continue;
        }else if(split == '/'){
            index=4;
        }else{//行尾了
            break;
        }
    }

    return ip(iparr[0],iparr[1],iparr[2],iparr[3],iparr[4]);
};

int cmp(const ip &a,const ip &b){
    if(a.a!=b.a)
    {
        return a.a<b.a;
    }else if(a.b!=b.b)
    {
        return a.b<b.b;
    }else if(a.c!=b.c)
    {
        return a.c<b.c;
    }else if(a.d!=b.d)
    {
        return a.d<b.d;
    }
    else
    {
        return a.suf<b.suf;
    }
}

bool issub(ip &a,ip &b){
    bitset<32> l = a.uip;
    bitset<32> r = b.uip;
    int i = 0;
    for(i=31;i>31-(a.suf);i--)//因为大小关系已经排列好了,所以a的前缀长度一定小于等于b
    {
        if(l[i] != r[i])//如果前缀范围内有一个不相等,那就不是子集关系
        {
            return false;
        }
    }
    return true;
}

bool merge(ip &a,ip &b)
{
    if(a.suf != b.suf || a.uip[32-a.suf] == 1 || a.suf == 0)
    {
        return false;
    }

    bitset<32> l = a.uip;
    bitset<32> r = b.uip;
    int i = 0;

    for(i=0;i<a.suf-1;i++)
    {
        if(l[31-i] != r[31-i])
        {
            return false;
        }
    }
    return true;
//    int rb = 31-a.suf-1; //因为相同的已经合并了,所以如果前面相同,最后一位肯定不同
//    不过再进行一次判断确认也可以,不影响结果
//    return (l[31-i]^r[31-i])==1;
}


int main(){
    list<ip> ips;

    int n;
    scanf("%d",&n);

    for(int i = 0;i<n;i++)
    {
        ip a = create();
        ips.insert(ips.end(),a);
    }
    ips.sort(cmp);
    int i;
    for(list<ip>::iterator iter = ips.begin(),next;iter!=ips.end();)
    {
        next = iter;
        next++;
        if(next==ips.end()) break;//如果cur已经是最后一个元素了就退出
        if(issub(*iter,*next))//如果有子集关系,一定next是iter的子集,因为大小关系已经按顺序匹配好了
            ips.erase(next);
        else
            iter++;//查看下一个元素
    }

    for(list<ip>::iterator iter = ips.begin(),next;iter!=ips.end();)
    {
        next = iter;
        next++;
        if(next==ips.end()) break;
        if(merge(*iter,*next))
        {
            ips.erase(next);
            iter->suf-=1;
            if(iter!=ips.begin())//如果cur的前面还有元素,则让cur--,因为可能有递归合并的可能
            {
                iter--;
                continue;
            }
        }
        iter++;//考虑下一个元素

    }

    for(list<ip>::iterator iter = ips.begin();iter!=ips.end();iter++)
    {
        iter->show();
    }

    return 0;
}

在这里插入图片描述

你可能感兴趣的:(算法)