用 PB_DS 实现一个只能单点修改的线段树,但又能动态插入和删除,现场赛的时候在对时间要求不大的情况下可以节约敲代码时间。
原理 #
原理就是 PB_DS 里的 tree 的最后一个模板参数定义了节点如何更新,我们可以通过自定义类让节点维护额外的信息(子树大小之类的)。需要定义额外信息类为metadata_type
,然后重载括号运算符来定义节点如何合并。通过树分裂实现区间查询,但有个问题就是分裂之后的树的大小是通过std::distance()
来计算的,对于 tree 的迭代器来说时间复杂度是O ( n ) O(n) O ( n ) 的,所以我们还要重载 s t d : : d i s t a n c e std::distance s t d :: d i s t an ce
例子:RMQ #
# include < bits/stdc++.h >
# include < ext/pb_ds/assoc_container.hpp >
# include < ext/pb_ds/tree_policy.hpp >
using namespace std ;
struct Node {
int size , min ;
} ;
template < typename node_const_iterator , typename node_iterator , typename cmp_fn , typename _Alloc >
struct tree_max {
typedef Node metadata_type ;
inline void operator() ( node_iterator it , node_const_iterator null ) const
{
auto& n = (Node & ) it . get_metadata () ;
n . size = 1 ;
n . min = ( * it) -> second ;
for ( auto& c : { it . get_l_child () , it . get_r_child ()}) {
if (c != null) {
n . size += c . get_metadata () . size ;
n . min = min ( n . min , c . get_metadata () . min ) ;
}
}
}
} ;
using Tree = __gnu_pbds :: tree < int , int , std :: less < int > , __gnu_pbds :: splay_tree_tag , tree_max > ;
using ti = Tree :: iterator ;
Tree * other ;
namespace std {
template <> iterator_traits < ti > :: difference_type distance < ti >(ti a , ti b) {
return other -> node_begin () . get_metadata () . size ;
}
}
void split ( Tree & a , Tree & b , int x ) {
other = & b ;
a . split ( x , b ) ;
}
int main () {
std :: ios :: sync_with_stdio ( false ) ;
std :: cin . tie ( nullptr ) ;
int n , q ;
cin >> n >> q ;
Tree tr ;
for ( int i = 0 ; i < n ; i ++ ) {
int x ;
cin >> x ;
tr . insert ({i , x}) ;
}
while ( q -- ) {
int l , r ;
cin >> l >> r ;
Tree B , C ;
split (tr , C , r - 1 ) ;
split (tr , B , l - 1 ) ;
cout << B . node_begin () . get_metadata () . min << '\n' ;
tr . join (B) ;
tr . join (C) ;
}
}
非分裂做法 #
对于可逆的信息(如区间和)我们可以通过在树上行走获得前缀信息,然后通过前个前缀信息得到区间信息。目前先贴个别人的链接 ,还没研究如何写的短点。