线段树

本文介绍了一种高效解决区间问题的数据结构——线段树。详细解释了线段树的基本概念、构造方法及其支持的操作,包括插入、删除、计数等,并通过实例展示了线段树的应用。
线段树的定义
 
经常遇到一些与区间有关的题目,这类题目用通常的方法很麻烦,但是有一种特殊的数据结构——线段树(Segment Tree可以很好地解决有关区间上的问题。
线段树是一棵二叉树,记为T(a,b),参数a,b表示区间[a,b],其中,b-a为区间的长度,记为L。
其根为:[a,b]
其左儿子为:[a,(a+b)/2]
其右儿子为:[(a+b)/2,b]
若L=1,则为叶子节点。
//结点类型
typedef struct node
{
    
int l, r;
    
int count;
    
//lbd:
    
//左端点被覆盖为true,未被覆盖为false
    
//rbd相同
    bool lbd, rbd;
    node 
*left, *right;
}
*nodeptr;

//线段树
//支持的操作有:
//insert(a, b) 插入一条线段[a,b]
//remove(a, b) 当[a, b]这条线段存在时,删除
//count(a, b)  统计[a, b]被覆盖的次数
//               count(a, b)总是返回[a, b]中最少的覆盖次数,如
//                   如果[a, b]仅被覆盖过一次,当[p, q]∈[a, b]
//               且[p, q]被覆盖,[a, p],[q, b]没有被覆盖时,
//               count(a, b)只返回1
//measure(a, b) 返回[a, b]间的测度(测度m指的是区间中线段覆盖过的长度)
//lines(a, b)    返回[a, b]间连续线段的数量
//几个例子:
//insert(3, 6), insert(4, 9), insert(11, 12)之后
//count(3, 6) = 1, count(4, 6) = 2, count(1, 4) = 0
//measure(1, 4) = 1, measure(3, 9) = 6, measure(5, 10) = 4
//lines(1, 9) = 1, line(5, 12) = 2
class Segment_Tree
{
private:
    nodeptr root;
    
//递归建树
    void create(nodeptr& current, int a, int b)
    
{
        
//初始化
        current = new node;
        current 
-> l = a; current -> r = b; current -> count = current -> lbd = current -> rbd = 0;
        
if (b - a == 1//到达叶子,结束
            current -> left = current -> right = NULL;
        
else //递归调用
        {
            nodeptr lc, rc;
            create(lc, a, (a
+b) / 2);
            create(rc, (a
+b) / 2, b);
            current 
-> left = lc;
            current 
-> right = rc;
        }

    }

    
//插入一条线段
    void insert(nodeptr p, int a, int b)
    
{
        
//用于计算lines
        p -> lbd |= (p -> l == a);
        p 
-> rbd |= (p -> r == b);
        
//刚好是这一段,直接更新
        if (p -> l == a && p -> r == b)
            p 
-> count++;
        
else
        
{
            
int m = (p -> l + p -> r) / 2;    //a, b的中点
            if (b <= m)
                insert(p 
-> left, a, b);    //需要在[a, m]插入[a, b]
            else if (a >= m)
                insert(p 
-> right, a, b);    //在[m, b]中插入[a, b]
            else    //二分插入
            {
                insert(p 
-> left, a, m);
                insert(p 
-> right, m, b);
            }

        }

    }

    
//删除一条线段
    void remove(nodeptr p, int a, int b)
    
{
        
//用于计算lines
        p -> lbd &= !(p -> l == a);
        p 
-> rbd &= !(p -> r == b);
        
//刚好是这一段,直接更新
        if (p -> l == a && p -> r == b)
            p 
-> count--;
        
else
        
{
            
int m = (p -> l + p -> r) / 2;    //a, b的中点
            if (b <= m)
                remove(p 
-> left, a, b);    //需要在[a, m]删除[a, b]
            else if (a >= m)
                remove(p 
-> right, a, b);    //在[m, b]中删除[a, b]
            else    //二分删除
            {
                remove(p 
-> left, a, m);
                remove(p 
-> right, m, b);
            }

        }

    }

    
//统计[a,b]被覆盖的次数
    
//有待研究
    int count(nodeptr p, int a, int b)
    
{
        
if (p -> r - p -> l == 1)
            
return p -> count;
        
else
        
{
            
int m = (p -> l + p -> r) / 2;
            
if (b <= m)
                
return count(p -> left, a, b) + p -> count;
            
else if (a >= m)
                
return count(p -> right, a, b) + p -> count;
            
else
                
return min(count(p -> left, a, m), count(p -> right, m, b)) + p -> count;
        }

    }

    
//计算[a,b]的测度
    
//结点的测度m指的是结点所表示区间中线段覆盖过的长度
    
//m = b - a (count > 0)
    
//m = 0 (count = 0且为叶结点)
    
//m = left.m + right.m (count = 0且为内部结点)
    int measure(nodeptr p, int a, int b)
    
{
        
if (count(a, b) > 0)
            
return (b - a);
        
if (b - a == 1)
            
return 0;
        
int m = (p -> l + p -> r) / 2;
        
return measure(p -> left, a, m) + measure(p -> right, m, b);
    }

    
//计算[a,b]的连续线段数
    
//连续线段数line指的是区间中互不相交的线段条数
    
//计算方法:
    
//line = 1 (count > 0)
    
//line = 0 (count = 0且为叶结点)
    
//line = left.line + right.line - 1 (count = 0且为内部结点,且left.rbd和right.lbd同时为1)
    
//line = left.line + right.line (count = 0且为内部结点,且left.rbd和right.lbd不同时为1)
    int lines(nodeptr p, int a, int b)
    
{
        
if (count(a, b) > 0)
            
return 1;
        
if (b - a == 1)
            
return 0;
        
int m = (p -> l + p -> r) / 2;
        
return lines(p -> left, a, m) + lines(p -> right, m, b) - (p -> left -> rbd & p -> right -> lbd);
    }

public:
    
//初始化时,指定线段树的长度
    Segment_Tree(int size)
    
{
        create(root, 
1, size);
    }

    
//接口
    void insert (int a, int b)
    
{
        insert(root, a, b);
    }

    
void remove (int a, int b)
    
{
        
if (count(a, b) > 0)
            remove(root, a, b);
    }

    
int count(int a, int b)
    
{
        
return count(root, a, b);
    }

    
int measure(int a, int b)
    
{
        
return measure(root, a, b);
    }

    
int lines(int a, int b)
    
{
        
return lines(root, a, b);
    }

}
;

 

当然,线段树还能实现别的操作,具体因题而异,大家可以自行扩充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值