CCF认证2018123-CIDR合并

本人初学,水平有限,若有不足,恳请赐教!

由于题目信息太多且有些复杂(简称看不懂或懒得看~),因此本题的解题思路主要参考:日沉云起:CCF认证 201812-3CIDR合并。本文在其思路的基础上对代码进行了一些修改。

 首先,自定义结构IP,其属性包括ip地址(ip)和前缀长度(len)。由于题目用到的ip地址全部都是ipv4的地址,考虑其特性可以使用32位无符号整数来进行存储和运算。将IP.ip默认设置为0有一个好处就是可以将标准型ip地址和后缀省略型ip地址的处理统一到一起。

然后,定义函数用于读取每条ip地址并返回一个IP结构对象。这里,我使用的是有穷状态机的思想来处理每个输入的字符串的。先是用枚举类型设置两个状态NUM和SIGN分别处理数字和符号(特别说明,在最开始我是设置的三个状态,一个是数字,一个是‘.’,最后一个是‘/’。但是后来我发现斜线的处理只是比点号的处理多了一句话,因此后来我将这两个状态给合并在一起了。)。考虑到点分十进制ip地址格式中的每个数字对应的分别是32位无符号整型数据的由高到低的每个字节,因此每从字符串读取一个完整的数字后,要将其进行位移运算(或者乘法运算)使其移动到对应的字节位置中。

接着就是根据提示设计相应的函数。这里要再次感谢参考博文的博主提供的设计思路。与参考文献不同的是,在判断匹配集是否为子集或者匹配集可否同级合并时我使用的是位移运算,在一定程度上简化了代码同时也提高了效率。

具体代码如下:

#include <iostream>
#include <string>
#include <list>

using namespace std;

//有穷状态机的状态,分别对应数字和符号
//由于ip地址起始位置必为数字,故可以直接设置NUM为起始状态
enum STATE {NUM, SIGN};

typedef struct IP
{
	unsigned ip = 0;
	int len = 32;
}IP;

//读入并处理ip地址字符串
IP read(string s)
{
	IP a;
	bool flag = false; //记录是否已经对斜线符号进行了处理
	STATE state = NUM;
	unsigned buf = 0; //缓存每次数据处理的结果
	unsigned rule = 0x01000000; //在存储数据到对应字节时进行位移所用的变量
	for(int i = 0; i < s.size(); i++)
	{
		switch(state)
		{
		case NUM: //处理数字
			if(isdigit(s[i])) buf = s[i] - '0' + buf * 10; //是数字就纳入缓存
			else //否则要令指针回退一格并转移状态到SIGN
			{
				i--; state = SIGN;
			}
			break;
		case SIGN: //处理符号
			if(isdigit(s[i]))
			{
				i--; state = NUM; break;
			}
			a.ip += buf * rule; //将缓存数据移动到正确位置并加入到ip中
			rule >>= 8; //下一个数据应位移的位数
			buf = 0; //缓存清空
			flag = (s[i] == '/') ? true : false;
			break;
		}
		if(i == s.size() - 1) //字符串在遍历到尾部时需要进行特殊处理
		{
			if(flag) //根据题意,如果已经处理过斜线则说明此ip地址是标准型或后缀省略型
			{
				a.len = buf; //此时buf存放的必定是前缀长度
			}
			else //否则此ip地址为长度省略型,此时buf存放的仍是ip地址数据,前缀长度需要自己计算
			{
				a.ip += buf * rule;
				rule >>= 8;
				while(rule)
				{
					a.len -= 8;
					rule >>= 8;
				}
			}
		}
	}
	return a;
}

//根据题意b的匹配集为a的匹配集的子集等价于
//a的前缀长度不大于b的且a和b的ip地址的前a.len位完全相同
bool isChildCollection(IP &a, IP &b)
{
	if(a.len > b.len) return false;
	if((a.ip ^ b.ip) >> (32 - a.len)) return false;
	return true;
}

void merge1(list<IP> &ipl)
{
	auto i = ipl.begin(), j = ipl.begin();
	j++;
	while(j != ipl.end())
	{
		if(isChildCollection(*i, *j)) j = ipl.erase(j);
		else { i++; j++; }
	}
}

//根据题意a与b为a`可以合并等价于
//a与b前缀长度相同且二者的前len位中只有最后一位不相同
bool unionCollection(IP &a, IP &b)
{
	if(a.len != b.len) return false;
	return ((a.ip ^ b.ip) >> (32 - a.len)) == 1u;
}

void merge2(list<IP> &ipl)
{
	auto i = ipl.begin(), j = ipl.begin();
	j++;
	while(j != ipl.end())
	{
		if(unionCollection(*i, *j))
		{
			j = ipl.erase(j);
			((*i).len)--;
			if(i != ipl.begin())
			{
				i--; j--;
			}
		}
		else { i++; j++; }
	}
}

int main()
{
	int n;
	cin >> n;
	list<IP> ipl;
	for(int i = 0; i < n; i++)
	{
		string s;
		cin >> s;
		ipl.push_back(read(s));
	}
	ipl.sort([](const IP &a, const IP &b){
		if(a.ip != b.ip) return a.ip < b.ip;
		return a.len < b.len;
	});
	merge1(ipl);
	merge2(ipl);
	for(auto &e : ipl)
	{
		unsigned a = 0xff000000, b = 0x00ff0000, c = 0x0000ff00, d = 0x000000ff;
		cout << ((e.ip&a) >> 24) << '.' << ((e.ip&b) >> 16) << '.' << ((e.ip&c) >> 8) << '.' << ((e.ip&d)) << '/' << e.len << endl;
	}
	return 0;
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值