微软2016实习生笔试--第二题403 Forbidden

#1289 : 403 Forbidden

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB

描述

Little Hi runs a web server. Sometimes he has to deny access from a certain set of malicious IP addresses while his friends are still allow to access his server. To do this he writes N rules in the configuration file which look like:

allow 1.2.3.4/30
deny 1.1.1.1
allow 127.0.0.1
allow 123.234.12.23/3
deny 0.0.0.0/0

Each rule is in the form: allow | deny address or allow | deny address/mask.

When there comes a request, the rules are checked in sequence until the first match is found. If no rule is matched the request will be allowed. Rule and request are matched if the request address is the same as the rule address or they share the same first mask digits when both written as 32bit binary number.

For example IP "1.2.3.4" matches rule "allow 1.2.3.4" because the addresses are the same. And IP "128.127.8.125" matches rule "deny 128.127.4.100/20" because 10000000011111110000010001100100 (128.127.4.100 as binary number) shares the first 20 (mask) digits with10000000011111110000100001111101 (128.127.8.125 as binary number).

Now comes M access requests. Given their IP addresses, your task is to find out which ones are allowed and which ones are denied.

输入

Line 1: two integers N and M.

Line 2-N+1: one rule on each line.

Line N+2-N+M+1: one IP address on each line.

All addresses are IPv4 addresses(0.0.0.0 - 255.255.255.255). 0 <= mask <= 32.


For 40% of the data: 1 <= N, M <= 1000.

For 100% of the data: 1 <= N, M <= 100000.

输出

For each request output "YES" or "NO" according to whether it is allowed.

样例输入
5 5
allow 1.2.3.4/30
deny 1.1.1.1
allow 127.0.0.1
allow 123.234.12.23/3
deny 0.0.0.0/0
1.2.3.4
1.2.3.5
1.1.1.1
100.100.100.100
219.142.53.100
样例输出
YES
YES
NO
YES

NO

由题意直观的可以想到以下做法:

  1. 设计一个结构体,描述配置规则;
  2. 按读入顺序保存读入的所有规则;
  3. 对于每一个待检验的IP,从头遍历规则,若匹配,返回规则的动作;否则,返回允许
代码如下
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std;

struct Rule           // 存放规则的结构体
{
	bool allow;
	unsigned int ip;
	int mask;
	Rule() : allow(false), ip(0), mask(32){}
};

unsigned int getIp()   // 获得输入的IP地址
{
	unsigned int a, b, c, d;
	char dot;
	cin >> a >> dot >> b >> dot >> c >> dot >> d;
	return (a << 24) | (b << 16) | (c << 8) | d;
}

bool solve(vector<Rule> &rules, unsigned int ip)   // 判断IP地址满足的规则
{
	for(auto rule : rules)
	{
		if(rule.mask == 0 || (ip ^ rule.ip) >> (32-rule.mask) == 0)
			return rule.allow;
	}
	return true;
}

int main()
{
	int N, M;
	while(cin >> N >> M)
	{
		char cmd[20];
		char t;
		vector<Rule> rules(N);
		for(int i = 0; i < N; i++)
		{
			cin >> cmd;
			rules[i].allow = strcmp(cmd, "allow") == 0;
			rules[i].ip = getIp();
			cin.get(t);
			if(t == '/')
				cin >> rules[i].mask;
			else
				cin.putback(t);
		}

		bool* result = new bool[M];
		for(int j = 0; j < M; j++)
		{
			unsigned int ip = getIp();
			result[j] = solve(rules, ip);
		}

		for(int i = 0; i < M; i++)
			cout << (result[i] ? "Yes" : "No" )<<endl;
		delete[] result;
	}
	return 0;
}

前缀匹配问题,可以用前缀树来解决。因此,可以利用前缀树来确定哪些规则可以适用于一个IP。

Trie Approach

于是有了下面的想法:

  1. 规则集利用前缀树描述
  2. 前缀树每个结点记录,到本结点是否为一个规则,规则要求的动作是什么,规则的序号。
  3. 对于一个IP,遍历前缀树,找到所适用规则中序号最小的,返回该规则的要求,若没有适用规则,返回允许。
  4. 一个IPv4的地址大小为32bit,也就是可以在常量时间找到适用规则。

至此,剩下的问题就是如果建立前缀树,由于题意要求,最早匹配原则,树的建立可以这样做:

  1. 若插入新规则i时,该规则的前缀路径上已有规则j, 则规则j的序号一定比i小,也即,规则i被j屏蔽,直接丢弃i即可。
  2. 注意第一条中,当规则i和j等长时,也适用。
代码如下
#include <iostream>
#include <cstring>

using namespace std;

class Trie     // 建立规则集的前缀树
{
private:
	struct TrieNode   // 前缀树节点
	{
		int order;   // 定义规则的序号
		TrieNode* children[2];   // 连接下一前缀树节点
		TrieNode() : order(0)
		{
			children[0] = children[1] = NULL;
		}
	};
	TrieNode *root;
public:
	Trie() : root(new TrieNode()){}
	void addRule(unsigned int ip, int mask, int order)  // 添加规则
	{
		TrieNode* pos = root;
		for(int i = 1; i <= mask; i++)
		{
			if(pos->order)    // 当前缀路径上已有规则时,直接返回,丢弃当前要插入的规则
				return;
			int bit = (ip >> (32-i)) & 1;
			if(!(pos->children[bit]))
			{
				TrieNode* newNode = new TrieNode();
				pos->children[bit] = newNode;
			}
			pos = pos->children[bit];
		}
		if(!(pos->order))
			pos->order = order;
	}

	int query(unsigned int ip)   // 查询ip满足的规则
	{
		TrieNode *pos = root;
		int order = 1;
		for(int i = 1; i <= 32; i++)
		{
			if(pos->order)
				order = pos->order;
			int bit = (ip >> (32-i)) & 1;
			if(!(pos->children[bit]))
				break;
			pos = pos->children[bit];
		}
		return order;
	}
};

inline unsigned int getIp()  // 得到输入的IP
{
	unsigned int a, b, c, d;
	char dot;
	cin >> a >> dot >> b >> dot >> c >> dot >> d;
	return (a << 24) | (b << 16) | (c << 8) | d;
}

int main()
{
	int N, M;
	Trie rule;
	while(cin >> N >> M)
	{
		char cmd[20];
		char t;
		for(int i = 1; i <= N; i++)
		{
			int mask = 32;
			cin >> cmd;
			unsigned int ip = getIp();
			cin.get(t);
			if(t == '/')
				cin >> mask;
			else
				cin.putback(t);
			int order = i;
			if(strcmp(cmd, "allow"))
				order *= -1;
			rule.addRule(ip, mask, order);
		}
		int *result = new int[M];
		for(int j = 0; j < M; j++)
		{
			unsigned int ip = getIp();
			result[j] = rule.query(ip);
		}

		for(int i = 0; i < M; i++)
			cout << (result[i] > 0 ? "Yes" : "No") <<endl;
		delete[] result;
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值