微软2016.04笔试(二):403 Forbidden

论scanf和cin的效率差别
这里写图片描述

unsigned int getIP(void)
{
    //char t;
    unsigned int a, b, c, d;
    //cin >> a >> t >> b >> t >> c >> t >> d;
    scanf("%d.%d.%d.%d", &a, &b, &c, &d);
    return((a << 24) | (b << 16) | (c << 8) | d);
}

定义结构体、结构体初始化、结构体变量的声明:

struct trie{
    int prior;
    trie* child[2];
    trie() { prior = 0; memset(child, 0, sizeof(child)); }
};

trie* root;

结构体变量初始化:

root = new trie();

这一句必须放在main()里,放在外面会报错:“此声明没有存储类或类型说明符”


完整代码:

/* 此题有O(m*n)的暴力解法,运行超时。
 * 使用前缀树,使查找一条记录的时间从O(n)变为O(1),总共O(min(n,m))。
 */

#include <iostream>
//hiho上面使用memset必须引用<cstring>
#include <cstring>
//VS2017只用<cstring>的话没法 cin >> string
#include <string>

using namespace std;

//前缀树
struct trie{
    int command;
    trie* child[2];//32位二进制ip地址,只有0或1的分支。
    trie() { command = 0; memset(child, 0, sizeof(child)); }
};

trie* root;
//注意root = new trie();语句不能放在这里。


//将输入的IPv4格式(128.127.8.125)转换成一个整数
unsigned int getIP(void)
{
    char t;
    unsigned int a, b, c, d;
    //利用char巧妙分开几个int,避免了使用string读入一整行再另作处理的麻烦。
    cin >> a >> t >> b >> t >> c >> t >> d;
    //这里的 移位 和 取或代替加法 很巧妙。
    return((a << 24) | (b << 16) | (c << 8) | d);
}

/* 
 * 将当前的一条规则加入到规则前缀树中
 * 因为检索时按顺序访问规则,所以后插入的规则始终不可覆盖已存在的规则。
 * 即,若规则1的终点在规则2的必经之路上,那么规则2没有存在的必要。
 * 因此,插入规则时,若途经(包括本规则的终点)已被标记为终点的节点,则应抛弃本规则。
 * 检索时,应寻找符合的最长路径,因为这必是该记录第一条匹配的规则。
 */

/*
 * 也可以不管上述规则,插入规则时只要终点不与之前的规则重合,就插入。
 * 但需要在节点中记录该规则是第几条,检索时需要一路记录遇到的编号最小的终点。
 */
void addIP(unsigned int ip, int mask, int command)
{
    trie* rule = root;
    int bit;
    for (int j = 0; j < mask; j++)
    {       
        //如果当前节点已经是某规则的终点
        if (rule->command) return;

        //从二进制最高位(记作第0位)开始,取第j位
        bit = ((ip >> (31 - j)) & 1);

        //如果下一个节点没被访问过,则分配空间
        if (!(rule->child)[bit])
        {
            (rule->child)[bit] = new trie();
        }

        //下面这句必须放在上面的if循环外。因为不管下一个节点是否已被访问过,当前的路径都需要继续访问。
        rule = (rule->child)[bit];
    }

    //下面的if条件不能省略。否则如果有先后两条相同ip的规则,后面的会覆盖前面的。
    if (!rule->command) 
        rule->command = command;

    return;
}

//检索某条IP记录
void checkIP(unsigned int ip)
{
    int bit;
    trie* rule = root;
    //command初始值设为1,这样如果没有匹配的规则,会输出"YES"。
    int command = 1;
    //需要走完当前IP的全部32位二进制,因为要找出最长的匹配路径。只有没路了才停下。
    for (int j = 0; j < 32; j++)
    {
        bit = ((ip >> (31 - j)) & 1);
        //随时记录遇到的最后一个终点
        if (rule->command)
        {
            command = rule->command;
        }
        //如果本记录走不下去了,即没有更早出现的匹配记录了
        if (!(rule->child)[bit]) break;
        //还能走,所以继续走
        rule = (rule->child)[bit];
    }
    //按规则给出allow或deny的指令
    cout << ((command > 0) ? "YES" : "NO") << endl;
    return;
}
int main()
{
    int n, m;
    string cmd;
    unsigned int ip;
    char t;
    int mask;

    root = new trie();

    cin >> n >> m;

    //建立前缀二叉树
    for (int i = 0; i < n; i++)
    {
        mask = 32;
        cin >> cmd;
        ip = getIP();
        cin >> t;
        if (t == '/')cin >> mask;
        //cin.putback(char)的用法
        else cin.putback(t);
        int command = (cmd[0] == 'a') ? 1 : (-1 );
        addIP(ip, mask, command);
    }
    //按规则检索IP地址,并输出结果
    for (int i = 0; i < m; i++)
    {
        ip = getIP();
        checkIP(ip);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值