【CCF 201812-3】CIDR合并(链表,位运算)

写在前面

本题考查的是对STL::list的熟练运用,以及对位运算的思考;思路在题目中给出的提示当中已经非常清晰。

解题思路

  • 将输入转化为list<前缀IP>

    struct Prefix
    {
      vector<unsigned> ip;
      int len;
    }
    

    这里要分两种特殊情况:省略后缀型和省略长度型;

    我们使用的技术包括:字符串流stringstream、子串函数string::substr()、string转int函数stoi();

  • 对链表进行排序

    由于List的迭代器并不是随机访问迭代器,因此我们在这里不能够使用STL::sort(),而只能使用list::sort();

  • 从小到大合并

    考察了对链表的遍历操作,更重要的是判断b是否为a的子集。下面是思路:

    • 将a和b的IP地址计算出来,它们分别是32位的比特。

    • 如果b.len > a.len,那么b必不可能是a的子集。

    • 分别取a和b比特流的前a.len位,若它们相等则b是a的子集

      具体的方法是计算一个掩码mask = 111111…00000,前面有a.len个1,后面有(32 - a.len)个0。

      unsigned mask = (a.len == 0) ? 0 : ~((1 << (32 - a.len)) - 1);

  • 同级合并

    考察了对链表的遍历操作,更重要的是判断a’是否与a∪b相等。下面是思路:

    • 进行合并的要求为a.len == b.len,我们假设a的IP地址的前缀是0010111,即a.len == 7

      按照题目的方法,a_new.len == 6,a_new的前缀是001011

      那么b要是多少才有可能让a_new == a∪b呢?很显然了,答案是0010110。即a和b的前缀仅有最后一位是相反的。

    • 用位运算表示为:

      unsigned mask = 1 << (32 - a.len);

      return (ip_b ^ mask) == ip_a;

满分代码(C++11,250ms)

// 开启O3优化
#pragma GCC optimize(3, "Ofast", "inline")

#include <cstring>
#include <iostream>
#include <list>
#include <sstream>
#include <vector>
using namespace std;

struct Prefix
{
    vector<unsigned> ip;
    int len;

    // 构造函数:根据ip字符串解析ip前缀
    Prefix(const string &str)
    {
        size_t pos = str.find('/');
        if (pos != string::npos)
        {
            len = stoi(str.substr(pos + 1));    // len

            string ip_str = str.substr(0, pos);
            stringstream ss(ip_str);
            unsigned num;
            while (ss >> num)       // 输入数字
            {
                ip.push_back(num);
                ss.get();           // 输入数字后面的"."
            }
        }
        else    // 省略长度型
        {
            stringstream ss(str);
            unsigned num;
            while (ss >> num)
            {
                ip.push_back(num);
                ss.get();
            }
            len = ip.size() * 8;    // 计算len
        }

        // ip地址有4个数字,不足4个在后面补0
        size_t left = 4 - ip.size();
        for (size_t i = 0; i < left; ++i)
            ip.push_back(0);
    }

    // 定义偏序,便于调用sort
    friend bool operator<(const Prefix &a, const Prefix &b)
    {
        if (a.ip != b.ip)
            return a.ip < b.ip;
        return a.len < b.len;
    }

    // 根据vector<unsigned> ip 计算 ip地址
    unsigned get_ip()
    {
        unsigned ip_num = 0, mul = 1;
        for (int i = 3; i >= 0; --i)
        {
            ip_num += mul * ip[i];
            mul *= 256;
        }
        return ip_num;
    }

    // b是否为a的子集
    bool is_subset_of(Prefix &a)
    {
        if (len < a.len)
            return false;

        unsigned ip_a = a.get_ip();
        unsigned ip_b = get_ip();

        unsigned mask = (a.len == 0) ? 0 : ~((1 << (32 - a.len)) - 1);

        return (mask & ip_a) == (mask & ip_b);
    }

    // 输出ip地址
    void print()
    {
        cout << ip[0] << '.' << ip[1] << '.' << ip[2] << '.' << ip[3] << '/' << len << '\n';
    }
};

// ip前缀列表
list<Prefix> pre_list;

// 第二步:按大小合并
void merge_by_size()
{
    for (auto a = pre_list.begin(); a != pre_list.end();)
    {
        auto b = next(a);
        if (b == pre_list.end())    // 如果a后面没有元素了,则可以退出
            return;
        if (b->is_subset_of(*a))    // 如果b是a的子集,则删除b(注意此时迭代器a不动)
            pre_list.erase(b);
        else
            ++a;
    }
}

// 第三步核心问题:判断a_new是否可以代替a∪b
bool isMergable(Prefix &a, Prefix &b, Prefix &a_new)
{
    unsigned ip_a = a.get_ip();
    unsigned ip_b = b.get_ip();

    unsigned mask = 1 << (32 - a.len);
    
    return (ip_b ^ mask) == ip_a;
}

// 第三步:同级合并
void merge_with_same_rank()
{
    for (auto a = pre_list.begin(); a != pre_list.end();)
    {
        auto b = next(a);
        if (b == pre_list.end())
            return;

        Prefix a_new(*a);
        if (--a_new.len < 0)    // 如果a'的ip地址不合法,则移动至下一个元素
        {
            ++a;
            continue;
        }

        // 如果a.len == b.len:且可以合并
        if (a->len == b->len && isMergable(*a, *b, a_new))
        {
            *a = a_new;         // 将a替换为a'
            pre_list.erase(b);  // 删除b
            if (a != pre_list.begin())      // 后退一位继续操作
                --a;
        }
        else
            ++a;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n;
    cin >> n;
    cin.get();

    // 处理输入的ip字符串
    string line;
    while (n--)
    {
        getline(cin, line);
        pre_list.push_back(Prefix(line));
    }

    // 三步处理
    pre_list.sort();
    merge_by_size();
    merge_with_same_rank();

    // 打印输出
    for (Prefix &p : pre_list)
        p.print();

    return 0;
}

如果对您有帮助,记得点个赞哟 (^U^)ノ

发布了95 篇原创文章 · 获赞 148 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/leelitian3/article/details/104328409