扫描线+可并堆全纪录

本文详细介绍了扫描线算法的应用及其在平面直角坐标系中的实现方式,并深入探讨了左偏树(可并堆)的基本原理及其实现细节,包括如何通过左偏树进行查询最大值、删除最大值等操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

两个看似没什么关联的数据结构,因为两者做的题都不多所以放到一起总结一下

扫描线

感觉扫面线的经典题目就是面积和周长,不过有时候题目的转化也是挺巧妙的
例如:给出若干有权点和一个矩阵,求矩阵能框住的最大权值
我们把每个点看做是一个矩阵,求这些矩阵∩的最大值,扫描线求最大值即可

经典例题:扫描线求矩形面积并
经典例题:扫描线求矩形周长并
经典例题:扫描线+主席树

由于扫描线多在平面直角坐标系中,所以我们build的时候有些变化
在update的时候,对于未标记的叶子结点要清空一下
注意add函数的写法

//l,r:线段树中的左右端点
//ml,mr:实际维护区间
//len:覆盖长度

void build(int bh,int l,int r) {
    t[bh].l=l; t[bh].r=r;
    t[bh].ml=Y[l]; t[bh].mr=Y[r];
    t[bh].len=0;
    if (l+1==r) return;                                //注意返回条件 
    int mid=(l+r)>>1;
    build(bh<<1,l,mid);
    build(bh<<1|1,mid,r);  
}

void update(int bh) {
    int lc=bh<<1;
    int rc=bh<<1|1;
    if (t[bh].cover>0) {
        t[bh].len=t[bh].mr-t[bh].ml;
    }
    else if (t[bh].l+1==t[bh].r){    //清空 
        t[bh].len=0;
    }
    else {
        t[bh].len=t[lc].len+t[rc].len;
    }
}

void add(int bh,point a) {
    if (t[bh].ml==a.yl&&t[bh].mr==a.yr) {
        t[bh].cover+=a.type;
        update(bh);
        return;
    }
    if (a.yr<=t[bh<<1].mr) add(bh<<1,a);               //  <=
    else if (t[bh<<1|1].ml<=a.yl) add(bh<<1|1,a);      //  <=
    else {                                             
        point v=a;
        v.yr=t[bh<<1].mr;
        add(bh<<1,v);
        v=a;
        v.yl=t[bh<<1|1].ml;
        add(bh<<1|1,v);
    }
    update(bh);
}

左偏树(可并堆)

左偏树讲解

左偏树神啊
看似是一个支持合并的堆,可以查询最大值(最小值),删除最大值(最小值),合并
然而就是这三个操作,强到不行啊
不会有出题人让你做可并堆的裸题的
但是如果我们发现一道题需要我们查询最大值(最小值),删除最大值(最小值),合并之类的
就可以考虑用可并堆(左偏树)完成啦

经典例题:左偏树(一)
经典例题:左偏树(二)
经典例题:左偏树(三)

因为我们要merge,所以需要用并查集维护每个堆的根结点(最小值)
merge函数虽然重要,但是变化比较少
合并两个堆的时候,维护并查集即可(还是比较好理解的)

重点在删除
我们每次删除一定是堆顶元素(也只能删除堆顶元素)
题目要求不同,我们的操作就有一些差别了

如果我们要彻底删除这个元素,那么在 merge(ch[now][0],ch[now][1]) m e r g e ( c h [ n o w ] [ 0 ] , c h [ n o w ] [ 1 ] ) 之后
要把 now n o w 元素的fa设为本身,重新把 now n o w 变成一个单元素集

void del(int x) {         //删除x所在堆中的最小值
    int now=find(x);
    int root=merge(ch[now][0],ch[now][1]);
    fa[root]=root;
    fa[now]=now;          //变成单元素 
}

然而,还存在另一种意义上的删除:
now n o w 的值,之后给 now n o w 打上一个标记(表示不能再取),在再次查找 now n o w 所在集合还是可以得到该集合当前可用的最小值

也就是说我们希望通过 now n o w 找到 ta t a 原来的集合

那么只要更改一下最后一句话,把 now n o w fa f a 设为当前集合的最小可用值即可

void del(int x) {
    int now=find(x);
    int root=merge(ch[now][0],ch[now][1]);
    fa[root]=root;
    fa[now]=root;
}
const int N=1000010;
int fa[N],n,m;
int val[N],ch[N][2],dis[N];
bool kill[N]; 

int find(int x) {
    if (fa[x]!=x) fa[x]=find(fa[x]);
    return fa[x];
}

int merge(int x,int y) {
    if (!x) return y;
    if (!y) return x;
    if (val[x]>val[y]) swap(x,y);    //小根堆
    ch[x][1]=merge(ch[x][1],y);
    if (dis[ch[x][1]]>dis[ch[x][0]]) swap(ch[x][0],ch[x][1]);   //维护左偏性质 
    dis[x]=dis[ch[x][1]]+1;
    return x; 
}

int Min(int x) {
    int t=find(x);
    return val[t];        //根结点就是最小值 
}

void together(int x,int y) {    //合并
    int f1=find(x),f2=find(y);
    if (f1==f2) return;
    int root=merge(f1,f2);
    fa[f1]=fa[f2]=root;
}

淘到了曲神的可并堆模板

struct heap {
    int ch[N][2], dis[N], val[N];
    int merge(int x, int y) {
        if (x * y == 0) return x + y;
        if (val[x] < val[y]) swap(x, y);
        ch[x][1] = merge(ch[x][1], y);
        if (dis[ch[x][0]] < dis[ch[x][1]])
            swap(ch[x][0], ch[x][1]);
        dis[x] = dis[ch[x][1]] + 1;
        return x;
    }
    int pop(int x) {
        return merge(ch[x][0], ch[x][1]);
    }
    void init(int x, int v) {
        ch[x][0] = ch[x][1] = dis[x] = 0;
        val[x] = v;
    }
} h;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值