Tunnel Warfare

本文介绍了一种利用线段树解决区间合并问题的方法,并通过一个具体的战争背景问题进行实战演示。文章详细解释了线段树的构建、更新及查询过程,特别关注连续村庄状态的变化与查询最大连续村庄数。

线段树之区间合并


第二次写博客,感觉熟练多了,谢谢大家的支持,我以后也会加油的!
这次延续我以往的代码风格,喜欢我的博客记得留言哦(^ω^)!
这次的题目是一个区间合并的题目,感觉有点不好理清,我会努力的把它说清楚的!


Question:

Description:
During the War of Resistance Against Japan, tunnel warfare was carried out extensively in the vast areas of north China Plain. Generally speaking, villages connected by tunnels lay in a line. Except the two at the ends, every village was directly connected with two neighboring ones.
Frequently the invaders launched attack on some of the villages and destroyed the parts of tunnels in them. The Eighth Route Army commanders requested the latest connection state of the tunnels and villages. If some villages are severely isolated, restoration of connection must be done immediately!
Input:
The first line of the input contains two positive integers n and m (n, m ≤ 50,000) indicating the number of villages and events. Each of the next m lines describes an event.
There are three different events described in different format shown below:
D x: The x-th village was destroyed.
Q x: The Army commands requested the number of villages that x-th village was directly or indirectly connected with including itself.
R: The village destroyed last was rebuilt.
Output:
Output the answer to each of the Army commanders’ request in order on a separate line.
Sample Input:
7 9
D 3
D 6
D 5
Q 4
Q 5
R
Q 4
R
Q 4
Sample Output:
1
0
2
4


Answer:

第一步、数据预处理:

#define MAX 50010

第二步、自定义数据类型:

我仍旧使用结构体数组表示线段树,结构体的成员包括该节点的区间范围,该区间内从左开始向右的的最大连续村庄数,从右开始向左的最大连续村庄数,以及该区间的最大连续村庄数。为什么要这么设置呢?根据题意我们知道,当前区间的最大连续村庄数有三种可能的情况:
1、左子树的最大连续村庄数
2、右子树的最大连续村庄数
3、左子树从右开始向左的最大连续村庄数+右子树从左开始向右的最大连续村庄数
因此,我们还需要保存该区间内从左开始向右的的最大连续村庄数,从右开始向左的最大连续村庄数。
题目还要求我们修复最近毁灭的村庄,就需要保存最近修复的村庄的位置,由于可能出现连续多次修复的情况,需要保存的位置也会有多个,进一步思考发现修复村庄的顺序和毁灭村庄的顺序满足先进后出的原则,因此栈是最适合的数据结构。这里我用数组来模拟一个栈,也可以采用其他方式来实现,比如C++STL的容器。
结构体定义如下:

struct Tree
{
    int left,right;
    int lsum,rsum,tsum;
}tr[MAX<<2];

栈的定义如下:

int stack[MAX],top=-1;//用数组来模拟栈 

第三步、main(),程序结构设计:

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)==2)//一如既往的支持多组输入 
    {
        build(1,1,n);//build()用于建立线段树,无返回值,参数有根节点(id=1),以及此节点表示的区间[l,r] 
        while(m--)
        {
            char order[2];
            scanf("%s",order);
            if(order[0]=='D')//毁灭一个村庄 
            {
                int pos;
                scanf("%d",&pos);
                updata(1,pos,1);//updata()用于数据更新,无返回值,参数有三个,分别是根节点(id=1),要更新的位置和更新的方式,这里有两种,1代表毁坏一个村庄,2代表修复一个村庄 
                stack[++top]=pos;//将这个位置保存下来,入栈,也就是记录下次修复是的位置 
            }
            else if(order[0]=='R')//修复一个村庄 
            {
                int pos;
                if(top>-1)//栈不为空 
                {
                    pos=stack[top--];//读取最近一次被毁灭的村庄 
                    updata(1,pos,2);//数据更新,这里第三个参数变为2,代表是修复村庄 
                }
            }
            else
            {
                int pos;
                scanf("%d",&pos);
                printf("%d\n",query(1,pos));//query()用于查询,参数为根节点(id=1),和要查询的位置,返回值是在要查询位置的最大连续村庄数 
            }
        }
    }
    return 0;
}

第四步、子函数的实现:

线段树的建立及其初始化:

//build()用于建立线段树,无返回值,参数有根节点(id),以及此节点表示的区间[l,r] 
void build(int id,int l,int r)
{
    tr[id].left=l;
    tr[id].right=r;
    tr[id].lsum=tr[id].rsum=tr[id].tsum=(r-l+1);//初始化因为村庄都是完好的所以这个区间的左最大连续区间数、右最大连续区间数以及该区间连续最大数都是区间长度呢 
    if(l<r)//没有到叶子节点 
    {
        int mid=(l+r)/2;
        build(id*2,l,mid);//建立左子树 
        build(id*2+1,mid+1,r);//建立右子树 
    }
}

线段树的更新(单点更新):

//updata()用于数据更新,无返回值,参数有三个,分别是根节点(id),要更新的位置和更新的方式,这里有两种,1代表毁坏一个村庄,2代表修复一个村庄 
void updata(int id,int pos,int memthod)
{
    if(tr[id].left==tr[id].right)//要更新的村庄 
    {
        if(memthod==1)//要毁灭这个村庄 
            tr[id].lsum=tr[id].rsum=tr[id].tsum=0;
        else//修复这个村庄 
            tr[id].lsum=tr[id].rsum=tr[id].tsum=1;
    }
    else//搜寻要更新的村庄 
    {
        int mid=(tr[id].left+tr[id].right)/2;
        if(pos<=mid)
            updata(id*2,pos,memthod);
        else
            updata(id*2+1,pos,memthod);
        //每次更新也要更新它所在的区间的连续村庄数哦 
        tr[id].lsum=tr[id*2].lsum;
        tr[id].rsum=tr[id*2+1].rsum;
        tr[id].tsum=max(tr[id*2].rsum+tr[id*2+1].lsum,max(tr[id*2].tsum,tr[id*2+1].tsum));
        //初始化回溯更新,但不要忘记还有两种特殊情况哦 
        if(tr[id*2].lsum==(tr[id*2].right-tr[id*2].left+1))//如果左子树全都是好的,那么它的父节点的左最大连续区间数还要加上右子树的左最大连续区间数 
            tr[id].lsum+=tr[id*2+1].lsum;
        if(tr[id*2+1].rsum==(tr[id*2+1].right-tr[id*2+1].left+1))//同理,右子树也一样 
            tr[id].rsum+=tr[id*2].rsum;
    }
}

查询函数:

//query()用于查询,参数为根节点(id=1),和要查询的位置,返回值是在当前位置的最大连续村庄数 
int query(int id,int pos)
{
    if(tr[id].left==tr[id].right||tr[id].tsum==0||tr[id].tsum==(tr[id].right-tr[id].left+1))//当查询到叶子节点或者该区间全部被毁灭或者该区间全都是完好的,返回这个区间的最大连续村庄数 
        return tr[id].tsum;
    else//搜寻该位置的最大连续村庄数能延伸的最大区间 
    {
        int mid=(tr[id].left+tr[id].right)/2;
        if(pos<=mid)//延伸到左子树 
        {
            if(pos>=(tr[id*2].right-tr[id*2].rsum+1))//左子树的村庄全都是完好的,那么还可以延续到右子树 
                return query(id*2,pos)+query(id*2+1,mid+1);
            else
                return query(id*2,pos);
        }
        else//同理,延伸到右子树 
        {
            if(pos<=(tr[id*2+1].left+tr[id*2+1].lsum-1))//右子树的村庄全都是完好的,那么还可以延续到左子树 
                return query(id*2+1,pos)+query(id*2,mid);
            else
                return query(id*2+1,pos);
        }
    }
}

这个题也顺利解决了呢,不知道各位看懂了吗?当然自己实现的话还有许多需要注意的细节,特别是合并区间时的两种特殊情况,连续修复,连续毁灭等等,查询函数一定要延伸到最大不能再延伸的时候为止,这样才能得到最大连续村庄数哦!
时空复杂度如下所示:

MemoryTimeLength
4004(Kb)592(Ms)2089(Bytes)

同样希望有大佬指点一下怎么进一步降低时空复杂度。我很期待哦(@ω@)!


下次也要加油!噢!
期待你们的评论!

<think>好的,我需要帮助用户深入了解Tunnel技术,特别是GRE Tunnel的实现原理和技术细节。用户已经提供了之前的对话历史,其中详细解释了GRE技术,并通过快递隧道的比喻进行了说明。现在用户想进一步了解Tunnel相关的技术细节或实现方式,尤其是GRE Tunnel的原理。 首先,回顾之前的回答,用户已经了解了GRE的基本工作原理、封装过程、应用场景以及优缺点。现在需要更深入地探讨技术实现,可能需要包括封装的详细步骤、协议头结构、配置示例以及与其他技术的对比。 用户提供的参考引用提到了OSPF与GRE Tunnel的配置命令,但用户现在更关注的是Tunnel技术本身的详解,而不是具体配置。因此,我需要集中讨论GRE Tunnel的实现机制,例如封装过程、协议号、头部字段的作用,以及可能涉及的MTU问题、安全性考虑等。 需要注意用户提到的站内引用[1]中的信息,确保引用相关内容,如GRE的多协议封装能力、隧道端点处理流程等。同时,用户希望尽可能参考之前的回答,因此需要保持比喻的一致性,比如继续使用快递隧道的类比来解释技术细节。 接下来,我需要组织内容,使其结构清晰,分点阐述。可能的结构包括:封装解封装流程、协议头结构、关键技术点(如协议号47、密钥验证、Keepalive机制)、与其他隧道协议的比较、典型应用场景中的技术实现,以及常见问题如MTU调整和故障排查。 还需要确保使用正确的中文术语,避免技术术语错误,同时保持解释的通俗易懂,符合用户之前接受的快递隧道比喻。此外,根据用户要求,所有行内数学表达式和独立公式需使用正确的LaTeX格式,但在这个主题中可能涉及的数学内容较少,更多是协议结构描述,因此需注意正确使用代码块和表格来展示技术细节。 最后,生成相关问题时要围绕用户可能感兴趣的扩展点,如GRE与IPSec的结合、与其他隧道技术的对比、性能优化等,确保问题具有针对性,帮助用户进一步探索。</think>### GRE Tunnel技术详解及实现原理 --- #### **一、Tunnel技术基础概念** 1. **隧道本质** - 逻辑上的**点对点直连通道**,物理上通过封装/路由实现,如: $$原始数据包 \xrightarrow{封装} [外层IP头][隧道协议头][原始数据]$$ - 类比:快递员将**异形包裹**装入标准纸箱(外层协议),确保跨区域运输[^1]。 2. **核心功能** - **协议转换**:支持IPv4/IPv6、IPX等异构协议互传 - **逻辑隔离**:在公共网络(如互联网)上构建私有数据通道 - **路径控制**:强制数据流经指定路径(如绕过防火墙策略) --- #### **二、GRE Tunnel实现原理** 1. **封装结构拆解** ```plaintext | 外层IP头部 (20B) | GRE头部 (4-16B) | 原始数据包 | ``` - **外层IP头**:源/目的地址为隧道两端接口IP(如`10.1.1.1 → 203.0.113.5`) - **GRE头**:包含协议类型、校验标识、密钥等关键字段(详见下表) | 字段 | 长度 | 功能说明 | |-----------------|-------|---------------------------------| | Flags/Version | 2 bits| 标识是否启用校验和/密钥(如`0x8000`启用密钥) | | Protocol Type | 16 bits| 原始数据协议类型(如`0x0800`=IPv4)[^3] | | Checksum/Offset | 可选 | 数据完整性校验(RFC2890扩展) | | Key | 4B | 隧道两端匹配的认证密钥 | 2. **协议交互流程** ```mermaid graph LR A[原始数据包] --> B{GRE封装} B -->|添加GRE头+外层IP头| C[GRE报文] C --> D[公网路由传输] D --> E{GRE解封装} E --> F[还原原始数据] ``` 3. **关键技术特性** - **协议号47**:IP头部`Protocol`字段值为47时标识GRE报文[^3] - **PMTU发现**:通过设置DF位避免分片,动态调整隧道接口MTU(默认1476字节) - **防环路机制**:隧道接口丢弃源IP属于本地的数据包(通过ACL实现) --- #### **三、典型实现方式(以Cisco设备为例)** ```cisco ! 创建Tunnel接口 interface Tunnel0 ip address 192.168.1.1 255.255.255.0 tunnel source 203.0.113.1 ! 隧道起点公网IP tunnel destination 198.51.100.2 ! 隧道终点公网IP tunnel mode gre ip ! 指定GRE over IP模式 keepalive 10 3 ! 每10秒发送Keepalive,3次失败判定隧道中断 ``` --- #### **四、进阶技术细节** 1. **分片处理逻辑** - 原始数据包超过PMTU时: - 若DF=1 → 丢弃并返回ICMP Fragmentation Needed - 若DF=0 → 外层IP分片传输,接收端重组后解封装 2. **QoS优先级继承** ```plaintext 外层IP头ToS字段 = 原始IP头ToS值 & 0xFC // 保留前6位优先级标记 ``` 3. **多协议支持原理** - **协议类型字段**:GRE头部`Protocol Type`标识原始数据协议(如`0x0800`=IPv4,`0x86DD`=IPv6) - **嵌套隧道**:GRE隧道内可承载其他隧道协议(如GRE over IPSec over MPLS) --- #### **五、与其他隧道技术对比** | 特性 | GRE | IPSec隧道模式 | VXLAN | |---------------------|-------------------|-------------------|------------------| | **封装开销** | 24-36B | 50-57B | 50B+ | | **协议支持** | 多协议 | 仅IP | 仅以太网帧 | | **安全性** | 无加密 | 强加密 | 依赖叠加层 | | **典型场景** | 跨协议互联 | 安全远程接入 | 数据中心大二层扩展 | --- ### 相关问题 1. GRE如何通过Key字段实现隧道端点认证?[^2] 2. 在MPLS网络中为何需要GRE隧道嵌套? 3. GRE Keepalive机制如何检测隧道中断? 4. 为何GRE不适合直接传输实时音视频流? [^1]: GRE通过外层IP头实现跨网络路由,原始协议数据被完全封装保护。 [^2]: Key字段提供简单的隧道端点验证,但需配合IPSec实现完整安全保障。 [^3]: 协议号47使得中间路由设备能正确识别并转发GRE报文。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值