线段树

功能

线段树是线段组成的(二叉)树,可以解决许多区间问题。因为他的灵活性和较高的效率(二叉树,效率为log2级别的),甚至常常可以取代树状数组和RMQ等区间算法(树状数组只能向上修改,向下取值,RMQ无法即时修正,而线段树这两个问题都可以解决)。需要注意的是线段分两种:不重叠和重叠(点和真正的线段)。下面以不重叠为例。

1.建树

首先tr[1].L肯定就是左端点,tr[1].R是右端点,然后就需要推左子树2和右子树3(和堆的编号规则一样)。那么左子树就是L~mid,右子树就是mid+1~R。下面给出代码:

void Build(int id,int L,int R)
{
    int mid=L+(R-L>>1); //取中
    tr[id].L=L;tr[id].R=R;
    if (L<=mid&&mid+1<=R) Build(id*2,L,mid),Build(id*2+1,mid+1,R);
    //递归造出左子树和右子树
}

2.修正

修正也是利用了分治的想法,对于加进来的线段L~R,我们需要通过分割把这个线段覆盖到建造好的线段树中去。先取当前线段树节点的mid,然后来进行分治:
1.R<=mid,那么就在右子树,到右子树中去处理。
2.L>mid,那么就在左子树,到左子树中去处理。
3.上面两个都不满足,那么L~R就被分割了,左子树和右子树都要处理。
下面给出代码:

void Insert(int id,int L,int R) //在id节点中加入L~R
{
    if (tr[id].L==L&&tr[id].R==R) //覆盖了这个节点
    {
        处理; //不同题目不同处理
        return;
    }
    int mid=tr[id].L+(tr[id].R-tr[id].L>>1); //中点
    if (R<=mid) Insert(id*2,L,R,x); else //左子树
    if (L>mid) Insert(id*2+1,L,R,x); else //右子树
    {
        Insert(id*2,L,mid,x);
        Insert(id*2+1,mid+1,R,x); //左右子树
    }
}

3.询问

和修正基本一样,就是分割的思想。

int Ask(int id,int L,int R) //在id这个节点
{
    if (tr[id].L==L&&tr[id].R==R) return 值; //不同题目不一样
    int mid=tr[id].L+(tr[id].R-tr[id].L>>1);
    if (R<=mid) return Ask(id*2,L,R); else //左子树
    if (L>mid) return Ask(id*2+1,L,R); else //右子树
    return 操作(Ask(id*2,L,mid),Ask(id*2+1,mid+1,R)); //不同题目不一样
    //左右子树
}

4.区间覆盖问题

给你一堆线段,问你L~R被覆盖了多少,这个就是典型的重叠线段树。详见例子中的“区间覆盖问题”。

5.区间极值问题

给你一堆带权值的线段,最后问你区间极值,那么最典型的就是RMQ问题了,但是RMQ只是点权而已,真正插入线段的话线段树是非常不错的选择。详见例子中的“区间极值问题”。
ps:内含lazy思想

6.动态统计问题

区间覆盖问题的拓展。

7.注意事项

首先有个问题就是线段树数组要开多大?按照拆分的规则2n就足够了,但是有可能就是最后有个冒尖的在下面,导致编号超出2n。所以我们要开4n。其次线段树的代码复杂度较高,且尽管是log2级别的但常数较大,所以能不用线段树尽量不要用。

模板(区间覆盖问题)

#include<cstdio>
const int maxn=100005,maxl=100005;
int n,L,R;
struct zzk
{
    int L,R; //L和R表示这个节点的范围
    bool cover; //cover表示这个节点有没有被覆盖
}tr[4*maxl];
void Build(int id,int L,int R) //建造id节点
{
    int mid=L+(R-L>>1);
    tr[id].L=L;tr[id].R=R;tr[id].cover=false;
    if (L<mid&&mid<R) Build(id*2,L,mid),Build(id*2+1,mid,R);
}
void Pushdown(int id) //传递,如果id节点被覆盖了,那么左子树和右子树就都覆盖了
{
    if (tr[id].R-tr[id].L==1||!tr[id].cover) return;
    tr[id*2].cover=true;tr[id*2+1].cover=true;
}
void Insert(int id,int L,int R) //在id节点插入L~R这条线段
{
    Pushdown(id); //传递下去,这里加不加其实都一样
    if (tr[id].cover) return;
    if (tr[id].L==L&&tr[id].R==R) //刚好重合
    {
        tr[id].cover=true;
        return;
    }
    int mid=tr[id].L+(tr[id].R-tr[id].L>>1); //中点
    if (R<=mid) Insert(id*2,L,R); else //左子树
    if (L>=mid) Insert(id*2+1,L,R); else //右子树
    {
        Insert(id*2,L,mid);
        Insert(id*2+1,mid,R); //左右子树
    }
}
int Ask(int id,int L,int R)
{
    Pushdown(id); //传递下去,不然就无法查找了
    if (tr[id].L==L&&tr[id].R==R&&tr[id].cover) return R-L; //刚好重合且这一段被覆盖了
    if (tr[id].R-tr[id].L==1) return 0; //到头了
    int mid=tr[id].L+(tr[id].R-tr[id].L>>1);
    if (R<=mid) return Ask(id*2,L,R); else
    if (L>=mid) return Ask(id*2+1,L,R); else
    return Ask(id*2,L,mid)+Ask(id*2+1,mid,R);
}
char getrch() {char ch=getchar();while (ch!='A'&&ch!='S') ch=getchar();return ch;}
int main()
{
    int i,x,y;
    char ch;
    freopen("sum.in","r",stdin);
    freopen("sum.out","w",stdout);
    scanf("%d%d%d",&n,&L,&R);
    Build(1,L,R); //建造一棵L~R的树
    for (i=1;i<=n;i++)
    {
        ch=getrch();scanf("%d%d",&x,&y);
        if (ch=='A') Insert(1,x,y);//插入x~y的线段
        if (ch=='S') printf("%d\n",Ask(1,x,y)); //询问x~y的覆盖个数
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值