模板:pb_ds指南

科技改变生活

前言

本来一直被畏于巨长的声明,没有学这个东西…
直到 棘手的操作 这道题,pb_ds模拟实现的两个log的做法不仅好写的一批,连时间竟然也把我单log的左偏树爆踩了???

我选择打不过就加入…

注意:本文一切服从于OI的实用性,可能会舍弃一些在OI领域对于我个人用处不大的功能。

哈希表的功能我选择舍弃了,因为哈希表本身并不是难于编写的结构。

auto 非常方便。

(可能会动态更新?)

头文件

pb_ds一族的万能头:

#include<bits/extc++.h>

就是把普通万能头的 std 改成了 ext (extend)。

总结

pbds的堆与stl相比,额外支持的函数如下:

  1. join(__gnu_pbds :: priority_queue &other): 把 other 合并到 *this 并把 other 清空。
  2. erase(point_iterator): 把迭代器位置的键值从堆中擦除。
  3. modify(point_iterator, const key): 把迭代器位置的 key 修改为传入的 key,并对底层储存结构进行排序。

宏定义&声明

(这里的宏定义只是笔者个人的习惯,下文会默认使用。)

namespace pd = __gnu_pbds;
#define It pd::priority_queue<int>::point_iterator
pd::priority_queue<int,cmp>q;

其中 point_operatorpb_ds 族所使用的迭代器。
cmp 是自定义的小于号,默认为 less<int>,也可以改为 greater<int>,或者使用和std类似的重载方式:

struct cmp{
  bool operator ()(const int &x,const int &y){return x<y;}
};
pd::priority_queue<int,cmp>q;

实际上比较符之后还有两栏可以自定义,但在OI应用中几乎都是默认即可(默认的堆实现方式是配对堆)。

成员函数

(完全复制 OI-wiki

  1. push(): 向堆中压入一个元素,返回该元素位置的迭代器。
  2. pop(): 将堆顶元素弹出。
  3. top(): 返回堆顶元素。
  4. size() 返回元素个数。
  5. empty() 返回是否非空。
  6. modify(point_iterator, const key): 把迭代器位置的 key 修改为传入的 key,并对底层储存结构进行排序,返回该元素位置的迭代器。
  7. erase(point_iterator): 把迭代器位置的键值从堆中擦除,返回值为 void
  8. join(__gnu_pbds :: priority_queue &other): 把 other 合并到 *this 并把 other 清空

复杂度

pushjoin O ( 1 ) O(1) O(1),其余均为最差 O ( n ) O(n) O(n)均摊 O ( log ⁡ n ) O(\log n) O(logn)

迭代器失效

点失效保证

与stl不同,pbds的迭代器非常强大。
只要迭代器没被删除,就始终保持有效。
即使执行过 modify 操作都是有效的!

未定义行为

点迭代器被删除,访问时依然不会RE,而是会返回0。
但是如果开启 -fsanitize=address,就会出现报错 heap-use-after-free

点迭代器被删除,再将其作为某个函数的参数传入时(如 modify erase),会直接RE。

平衡树

总结

优势:查询排名和k大。
似乎还有一些更加神奇的操作?但是经过 维持记忆开销和获得效果之间的性价比 权衡后,我并不想get了…

注意:pbds的tree必须是不可重集合!否则不仅会吞掉重复元素,在合并两棵树的时候还会发生不可预知的错误。具体实现上,可以使用 double 或者 pair 来解决该问题(个人更倾向于后者)。

声明

pd::tree<key,mapped,cmp ,pd::rb_tree_tag,pd::tree_order_statistics_node_update>q;

key:排序所用的键值类型。
mapped:映射类型。如果写成 null_type 则是无映射类型,相当于 std::set,否则就有些类似 std::map
pd::rb_tree_tag :底层使用红黑树的实现,看OIwiki和于纪平的课件各项指标都不错,就背这一个吧。
pd::tree_order_statistics_node_update:统计更新树的节点排名,没有它就没法支持查排名和k大!所以必须用…(首字母记忆:脱欧是内忧

成员函数

  1. insert(key):向树中插入一个元素 x(若为map则是pair),返回 std::pair<point_iterator, bool>
  2. erase(key/iterator):从树中删除一个元素/迭代器 x,返回一个 bool 表明是否删除成功。
  3. order_of_key(key):返回 x 以 cmp 比较的排名(unsigned long)。(排名)
  4. find_by_order(int):返回 cmp 比较的排名所对应元素的迭代器。(k
  5. lower_bound(key):以 cmp 比较做 lower_bound,返回迭代器。
  6. upper_bound(key):以 cmp 比较做 upper_bound,返回迭代器。
  7. join(tree):将 x 树并入当前树,前提是两棵树的类型一样,x 树被删除
  8. split(x(key),b(tree)):先清空 b 树,再以 cmp 比较,小于等于 x 的属于当前树,其余的属于 b 树。
  9. empty():返回是否为空。
  10. size():返回大小。

这里的“排名”是指大于该元素的元素个数。

<think>嗯,我现在需要分析这段C代码,理解它的功能。首先,我看到函数名是get_tb_width_height_in_pb,参数包括pb_w、pb_h、pb_part_size、pb_part_idx,还有输出的tb_w和tb_h,以及一个条件编译的tb_idx。这应该是在处理视频编码中的块划分,比如HEVC中的变换块(TB)或预测块(PB)的尺寸计算。 首先,函数根据pb_part_size的不同case来计算tb_w和tb_h。pb_part_size可能是块的分割方式,比如SIZE_2NxnU、SIZE_2NxnD等。每个case对应不同的分割模式,计算子块的宽度和高度。 比如第一个case是SIZE_2NxnU。这里,tb_w直接等于pb_w,而tb_h则根据pb_part_idx是否为0来决定。如果pb_part_idx是0,tb_h等于pb_h,否则是pb_h的三分之一。不过这里有一个条件编译DT_TRANSFORM,如果有定义的话,还会根据tb_idx进一步调整高度,比如分成三分之一或三分之二。这说明当启用了某种变换时,分割方式可能更复杂,比如分成三个部分而不是两个。 接下来是SIZE_2NxnD的情况,处理方式类似,但pb_part_idx的判断是1的时候,tb_h等于pb_h,否则是三分之一,同样在DT_TRANSFORM下会有不同的计算。这说明这两种分割模式是上下分割,可能是上半部分和下半部分的不同处理。 然后是SIZE_nLx2N和SIZE_nRx2N,这时候处理的是宽度tb_w,而高度tb_h保持不变。同样,根据pb_part_idx的值,可能取原宽度或三分之一,而条件编译下的tb_idx会影响具体的计算,比如分成三个部分。这可能对应左右分割的情况,左边或右边的块。 其他的case如SIZE_2NxhN、hNx2N、2Nx2N,则直接将tb_w和tb_h设为原pb的尺寸,说明这些分割模式下子块和父块尺寸相同。最后的default情况断言失败,说明传入的pb_part_size必须是已知的类型。 需要确认每个枚举值对应的分割方式。例如,SIZE_2NxnU可能是将父块分成上下两部分,上面是2N的一部分,下面是一个较小的部分。而nU可能表示非对称分割,比如上面是2N高度的一部分,下面是剩余的三分之一?或者可能是不同的划分方式。 条件编译DT_TRANSFORM可能启用了某种特殊的变换分割,比如更灵活的分块方式,这时候tb_idx用来选择子块的三分之一位置。例如,当tb_idx为0时,取三分之一的高度或宽度,而tb_idx为1时,取三分之二,这样总共有三个子块? 在代码中的(pb_h / 3) << 1,相当于乘以2,所以实际上是三分之二的高度。比如,当pb_h是3的倍数时,分成1/3和2/3两部分。这可能用于更细致的划分,例如将父块分成三个部分,根据tb_idx选择不同的部分。 可能这段代码的作用是根据父块的尺寸和分割类型,计算当前子块(TB)的宽度和高度。不同的分割类型(如上下、左右、对称等)会影响子块的尺寸,而条件编译的DT_TRANSFORM则可能支持不同的分割策略,比如将非对称分割分成三个部分而不是两个。 需要进一步理解每个case对应的分割模式,比如SIZE_2NxnU中的“nU”可能代表“n Up”,即上面的非对称分割。类似地,nD是下面的,nL和nR是左边和右边的。而每个分割模式下的tb_part_idx可能表示当前处理的是主块还是剩余部分。 例如,在SIZE_2NxnU的分割中,当pb_part_idx为0时,子块是上面的主部分,占据整个宽度和全部高度;当pb_part_idx为1时,子块是下面的部分,高度是原高度的三分之一。但如果启用了DT_TRANSFORM,当tb_idx为0时,可能取三分之一高度,而tb_idx为1时取三分之二,这样可能允许更多的子块划分方式。 总结来说,这个函数的作用是根据父块的分割模式、索引以及可能的变换选项,计算出当前子块的宽度和高度。不同的分割模式对应不同的划分方式,而条件编译的选项则扩展了某些分割模式下的子块尺寸计算方法。</think>以下是对该函数的逐行分析: 1. **函数声明** ```c void get_tb_width_height_in_pb(int pb_w, int pb_h, PART_SIZE pb_part_size, int pb_part_idx, int *tb_w, int *tb_h #if DT_TRANSFORM , int tb_idx #endif ) ``` - **功能**:根据父块参数计算子块(TB)的宽高 - **参数**: - `pb_w/pb_h`:父块的宽/高 - `pb_part_size`:父块划分方式(枚举类型) - `pb_part_idx`:子块索引(用于判断上下/左右区域) - `*tb_w/*tb_h`:输出子块宽高 - `tb_idx`(条件编译):当启用DT_TRANSFORM时的子块索引 2. **主逻辑结构** ```c switch (pb_part_size) { // 不同划分方式的分支处理 } ``` 根据父块划分方式执行不同计算逻辑 3. **SIZE_2NxnU 分支** ```c case SIZE_2NxnU: *tb_w = pb_w; // 宽度保持父块宽度 #if DT_TRANSFORM *tb_h = pb_part_idx == 0 ? pb_h : (tb_idx == 0 ? pb_h / 3 : (pb_h / 3) << 1); #else *tb_h = pb_part_idx == 0 ? pb_h : pb_h / 3; #endif ``` - **功能**:处理垂直非对称划分(上侧主区域) - **逻辑**: - 当`pb_part_idx=0`时:子块高度=父块高度(主区域) - 否则:普通模式取1/3高度,DT_TRANSFORM模式下: - `tb_idx=0`:1/3高度 - `tb_idx=1`:2/3高度(通过左移1位实现) 4. **SIZE_2NxnD 分支** ```c case SIZE_2NxnD: *tb_w = pb_w; #if DT_TRANSFORM *tb_h = pb_part_idx == 1 ? pb_h : (tb_idx == 1 ? pb_h / 3 : (pb_h / 3) << 1); #else *tb_h = pb_part_idx == 1 ? pb_h : pb_h / 3; #endif ``` - **功能**:处理垂直非对称划分(下侧主区域) - **逻辑**与SIZE_2NxnU对称 5. **水平划分分支** ```c case SIZE_nLx2N: // 左侧主区域水平划分 case SIZE_nRx2N: // 右侧主区域水平划分 // 类似垂直划分逻辑,但操作宽度维度 ``` - **SIZE_nLx2N**: - `pb_part_idx=0`时取全宽,否则: - 普通模式:1/3宽度 - DT_TRANSFORM:根据`tb_idx`取1/3或2/3宽度 - **SIZE_nRx2N**逻辑对称 6. **常规划分分支** ```c case SIZE_2NxhN: case SIZE_hNx2N: case SIZE_2Nx2N: *tb_w = pb_w; *tb_h = pb_h; // 保持原始尺寸 ``` - **功能**:处理对称划分或特殊比例划分 7. **错误处理** ```c default: assert(0); // 确保传入合法划分类型 ``` **关键算法说明**: 1. **非对称划分计算**:通过条件运算符实现类似分段函数: $$ \text{height} = \begin{cases} H & \text{if 主区域} \\ H/3 & \text{普通子区域} \\ \text{动态计算} & \text{DT\_TRANSFORM模式} \end{cases} $$ 2. **位运算优化**:`(pb_h / 3) << 1`等效于: $$ \text{height} = \frac{pb\_h}{3} \times 2 $$ **典型应用场景**: 该函数常用于视频编码标准(如H.265/HEVC)中,用于计算变换单元(TU)或预测单元(PU)的尺寸划分,支持多种划分模式: - 对称划分(2Nx2N) - 非对称划分(2NxnU/2NxnD/nLx2N/nRx2N) - 特殊比例划分(hNx2N/2NxhN) **条件编译的作用**: `DT_TRANSFORM`宏控制是否启用增强的变换划分模式。当启用时: - 允许更细粒度的划分(3段式划分) - 通过`tb_idx`参数选择子区域 - 典型应用:支持高级编码工具如ISP(Intra Sub-Partitions)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值