两个看似没什么关联的数据结构,因为两者做的题都不多所以放到一起总结一下
扫描线
感觉扫面线的经典题目就是面积和周长,不过有时候题目的转化也是挺巧妙的
例如:给出若干有权点和一个矩阵,求矩阵能框住的最大权值
我们把每个点看做是一个矩阵,求这些矩阵∩的最大值,扫描线求最大值即可
经典例题:扫描线求矩形面积并
经典例题:扫描线求矩形周长并
经典例题:扫描线+主席树
由于扫描线多在平面直角坐标系中,所以我们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;