线段树分治的一系列(?)的题的讲解

于是,我发现我不会线段树分治。
P5787 模板题
题目描述:
有一个
要求出
首先,考虑暴力怎么写。
暴力的程序非常好想。只要在每一个时间段内,diā出所有在这个时间段里面存在的边。然后连边。然后扩展域并查集判断是否为二分图。Over.
何为扩展域并查集判断是否为二分图?
二分图里面有一堆点。我们把每一个点
拆成两个点:原点和反点。他们的编号分别为 和 。 二分图里面有一堆边。如果点
和点 存在一条边,那么就在并查集里把 和 按秩合并。再把 和 合并。 那么如何判断什么时候不是二分图?只要在连边前判断
和 (都是原点)是否在同一连通块里即可。如果在,那么就不是一个二分图。
于是时间复杂度 你过不了此题!
考虑优化。
怎么优化呢?我们可以把每一个时间段都拍到线段树上。就比如说,一条连接了点
简要代码:
1 | void insert(int x, int ql, int qr, pii edge){ |
然后你想查在某一个时间段
好像又没怎么优化?别慌还有!线段树分治还没用到!
判断的时候就可以使用线段树分治了。
考虑到二分图的一个性质:如果你这个图不是二分图,那你后面不管怎么加边,他一定不是二分图。
我们遍历到一个段 No
。
反之,如果加完了这个 Yes
。
这个时候,发现了一个问题:一个节点,有两个儿子。他遍历完了左儿子想遍历右儿子,很明显的要撤销左儿子遍历过的边。怎么撤销呢?
我们维护一个栈,当合并
1 | struct Cglg{ |
这样也就很明显的,不能路径压缩。所以找父亲的函数就只能这样:
1 | int getfa(int x){ |
于是就讲完了。
代码如下:
1 |
|
P5214
这道题其实和模板题差不多。但是之前我做到这里的时候有一个疑问:
我用的是 map 存储的每一条边开始出现的位置,最后有一些边是还没有被删的,那么怎么遍历他们把他们加进线段树呢?
这时,神奇的迭代器:iterator
出现了!!!
迭代器就是和名字一样,不止是 map
,所有的 STL 容器都可以用 iterator
来遍历一遍。
而我们经常见到的:
1 | vector<int> edge[N]; |
就是一种简化了的迭代器。
迭代器原本的写法是这样的:
定义迭代器(这个类型要和被迭代的容器的类型完全相同!)
1 | // typedef pair<int, int> pii; |
遍历容器则是这样的(以 map
为例):
1 | for(it = mp.begin();it!=mp.end();it++){ |
其中 ->first
是迭代器目前指向的 Key, ->second
是迭代器目前指向的 Value。
配合着这个就能完美加边了=ω=
- Title: 线段树分治的一系列(?)的题的讲解
- Author: 11490DX
- Created at : 2024-11-18 21:15:01
- Updated at : 2025-04-30 07:35:13
- Link: https://11490dx.net/2024/11/18/segtree-divide/
- License: This work is licensed under CC BY-NC-SA 4.0.