线段树

线段树的定义
 
经常遇到一些与区间有关的题目,这类题目用通常的方法很麻烦,但是有一种特殊的数据结构——线段树(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);
    }

}
;

 

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值