946 字
5 分钟
[BZOJ 4523][Cqoi2016]路由表 Trie树 单调栈
2017-06-13

Description#

路由表查找是路由器在转发IP报文时的重要环节。通常路由表中的表项由目的地址、掩码、下一跳(Next Hop)地址和其他辅助信息组成。例如: 当路由器收到一个IP报文时,会将报文中的目的IP地址与路由表中的表项逐条进行比较,选 择匹配且最明确的表项,将报文转发给该表项中指定的下一跳。

匹配的过程是将报文中的目的地址和表项中的目的地址分别转为二进制串,再查看表项中的掩 码长度,若掩码长度为x,则将两个二进制串的前x位进行比较,如果相同则认为匹配。 所谓最明确是指在有多个表项匹配时,总是掩码长度最大的表项。也可以理解为匹配的二进制 位最多的项。 IP地址转为二进制串的操作是把地址中4个整数(一定在y到255的范围内)分别转为8位 二进制数,再顺序拼接起来,得到一个32位的二进制串。例如,192.168.1.253转为二进制串后为 11000000 10101000 00000001 11111101 我们以报文的目的地址为8.8.8.8为例,说明其在上述路由表的匹配过程。 上表将地址均转为二进制串,并用红色标记出待比较的位(由掩码长度决定)。将红色部分与 报文中的目的地址比较,可知0.0.0.0/1、8.8.8.0/24、8.8.8.8、32均能够匹配。路由器从中选取掩 码长度最长(/32)的表项8.8.8.8/32,将报文转发给其对应的下一跳地址192.168.1.253。 在实际的核心路由器中,路由表通常较大(现在互联网的全局路由表已经接60万条记录), 并且会随着新接入设备不断扩张。为了分析路由表变化对转发产生的影响,网络工程师想要知道 一段时间内某个IP地址的路由表项选择发生了多少次变化(变化是指由于最明确匹配等因素选择 了不同的表项,不考虑下一跳地址)。

Input#

第一行为整数M,表示共有M次操作。接下来M行,每行描述一次操作。操作有两种:
A D/L
其中.为一个IP地址,G为整数(1≤L≤32)。添加一条表项至路由表,其目的地址为 D掩码长度为L。下一跳地址由于没有用到,故省略。
Q D a b
其中D为一个IP地址,a,b为正整数(a≤b)。查询从第a次至第b次添加表项期间(含a、b),
目的地址D的路由表项选择发生了多少次变化。保证查询时表中至少有b个表项。
N<=10^6数据保证不会重复添加目的地址和掩码长度都相同的表项。

Output#

包含若干行,每行仅有一个整数,依次对应每个查询操作。

题面

题解#

以要查询的IP建一颗Trie树在终止节点打一个时间标记
每次查询用单调栈维护

/*
 * @Author: WildRage 
 * @Date: 2017-06-13 17:54:04 
 * @Last Modified by:   WildRage 
 * @Last Modified time: 2017-06-13 17:54:04 
 */
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct Trie{
    int T;
    Trie *ch[2];
    void *operator new (size_t size);
}*root,*num,*C;
void* Trie::operator new(size_t size){
    if(C==num){
        C=new Trie[1<<10];
        memset(C,0,sizeof(Trie)*(1<<10));
        num=C+(1<<10);
    }
    return C++;
}
int Index=0,cnt;
void insert(char *s){
    Trie *now=root;
    for(int i=0;i<cnt;i++){
        if(now->ch[s[i]-'0']==NULL)now->ch[s[i]-'0']=new Trie;
        now=now->ch[s[i]-'0'];
    }
    now->T=++Index;
}
int st[10005];
int query(char *s,int l,int r){
    int h=0;
    Trie *now=root;
    for(int i=0;i<cnt;i++){
        if(now->ch[s[i]-'0']==NULL)break;
        now=now->ch[s[i]-'0'];
        if(!now->T)continue;
        if(now->T>r)continue;
        if(now->T<l)while(h>=1)h--;
        if(now->T>=l&&now->T<=r){
            while(h>=1&&now->T<st[h])h--;
            st[++h]=now->T;
        }
    }
    return h;
}
int main()
{
    int m;
    scanf("%d",&m);
    char S[2],s[105];
    int a[5],b,l,r;
    root=new Trie;
    for(int i=1;i<=m;i++){
        scanf("%s",S);
        if(S[0]=='A'){
            cnt=0;
            scanf("%d.%d.%d.%d/%d",&a[1],&a[2],&a[3],&a[4],&b);
            for(int i=1;i<=4;i++){
                for(int j=7;j>=0;j--){
                    if(cnt+1>b)break;
                    if((a[i]>>j)&1)s[cnt]='1';
                    else s[cnt]='0';
                    cnt++;
                }
            }
            insert(s);
        }
        else{
            cnt=0;
            scanf("%d.%d.%d.%d %d %d",&a[1],&a[2],&a[3],&a[4],&l,&r);
            for(int i=1;i<=4;i++){
                for(int j=7;j>=0;j--){
                    if((a[i]>>j)&1)s[cnt]='1';
                    else s[cnt]='0';
                    cnt++;
                }
            }
            printf("%d\n",query(s,l,r));
        }
    }
}
[BZOJ 4523][Cqoi2016]路由表 Trie树 单调栈
https://www.nekomio.com/posts/6/
作者
NekoMio
发布于
2017-06-13
许可协议
CC BY-NC-SA 4.0