Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Segment Tree Beats #90

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open

Conversation

chihhsi
Copy link

@chihhsi chihhsi commented Jun 21, 2023

  • Read Contribute to NTHU-CPP.
  • Rebase to the latest main branch.
  • List all the references.
  • Successfully build the website by mdBook with no error after the change.
  • Exclude any irrelevant files.
  • Link to the issue which is related to your change.
  • Review the content by myself.
  • Pass tests/CI.

@chihhsi chihhsi changed the title Segment beats Segment Beats Jun 21, 2023
@chihhsi chihhsi changed the title Segment Beats Segment Tree Beats Jun 21, 2023
@chihhsi
Copy link
Author

chihhsi commented Jun 21, 2023

@harry900831 Waiting review

src/SUMMARY.md Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to add this file

src/data_structure/segment_tree_beats.md Outdated Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Show resolved Hide resolved
Comment on lines 39 to 55
<img src="image/segment_tree_beats/1-0.jpg" width="600" style="display:block; margin: 0 auto;"/>

從根節點 \\( 4 | 3 \\) 出發向下走,遇到左子節點 \\( 2 | 1 \\),由於 \\( mx1 \leq 2 \\),屬於情況一,直接退出;遇到右子節點 \\( 4 | 3 \\),由於 \\( mx2 \geq 2 \\),屬於情況三,對子節點進行遞迴搜尋。

<img src="image/segment_tree_beats/1-1.jpg" width="600" style="display:block; margin: 0 auto;"/>

繼續從右子節點 \\( 4 | 3 \\) 向下遞迴搜尋,遇到左子節點 \\( 4 | -1 \\),由於 \\( mx2 < 2 < mx1 \\),屬於情況二,區間和被改為 \\( 4 + 1 \cdot (2 - 4) = 2 \\),最大值被更新為 \\( 2 \\),標記也被設為 \\( 2 \\);遇到右子節點 \\( 4 | -1 \\),由於 \\( mx2 < 2 < mx1 \\),屬於情況二,同理。

<img src="image/segment_tree_beats/1-2.gif" width="600" style="display:block; margin: 0 auto;"/>

接著向上更新標記,節點 \\( 4 | 3 \\) 的區間和更新為 \\( 2 + 2 = 4 \\),最大值更新為 \\( 2 \\),最小值更新為 \\( -1 \\)。

<img src="image/segment_tree_beats/1-3.gif" width="600" style="display:block; margin: 0 auto;"/>

最後向上更新標記,根節點 \\( 4 | 3 \\) 的區間和更新為 \\( 2 + 2 = 4 \\),最大值更新為 \\( 2 \\),最小值更新為 \\( max(1, -1) = 1 \\)。

<img src="image/segment_tree_beats/1-4.gif" width="600" style="display:block; margin: 0 auto;"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這裡都講太細了
沒有這個必要


如下圖所示,左圖是一棵建立在 \\( [1, 4] \\) 上的線段樹,每一個節點紀錄的資訊的左側是區間最大值,右側是嚴格次大值。現在我們要讓區間 \\( [1, 4] \\) 對 \\( 2 \\) 取 \\( min \\)。那麼左圖中紅色邊表示搜尋時經過的邊,紅色字體的節點表示搜索終止的節點,右圖為更新後的線段樹。

<img src="image/segment_tree_beats/update_min.gif" width="600" style="display:block; margin: 0 auto;"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你把以下的圖濃縮到這一張內 就好

因為這一個演算法沒有很難懂,難的事他的時間複雜度為什麼是對的

2. 考慮一次標記下傳,只讓標記類 \\( T \\) 的權值 \\( w(T) \\) 增加 \\( O(1) \\)。
3. 當 \\( mx2 \geq x \\) 時,也就是情況三發生時,要進行遞迴搜尋,因為父節點的標記一定跟其中一個子節點一樣,所以每到一個節點至少回收一個標記,那麼 \\( \Phi(x) \\) 減少了 \\( O(1) \\)。

<img src="image/segment_tree_beats/2-4.gif" width="600" style="display:block; margin: 0 auto;"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我覺得你的 gif 都動好快,可以考慮每一張圖片放慢一點

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

然後看有沒有什麼方式讓讀者知道 現在是從新開始了

Comment on lines 109 to 133
```cpp
struct STB {
#define ls (u << 1)
#define rs (u << 1 | 1)

struct Node {
ll sum;
int l, r, mx1, mx2, cmx, tag;
} T[N << 2];

void pushup(int u) { // 向上更新標記
T[u].sum = T[ls].sum + T[rs].sum;
if (T[ls].mx1 == T[rs].mx1){
T[u].mx1 = T[ls].mx1;
T[u].cmx = T[ls].cmx + T[rs].cmx;
T[u].mx2 = max(T[ls].mx2, T[rs].mx2);
} else if (T[ls].mx1 > T[rs].mx1) {
T[u].mx1 = T[ls].mx1;
T[u].cmx = T[ls].cmx;
T[u].mx2 = max(T[ls].mx2, T[rs].mx1);
} else {
T[u].mx1 = T[rs].mx1;
T[u].cmx = T[rs].cmx;
T[u].mx2 = max(T[ls].mx1, T[rs].mx2);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove Chinese comment for template code

struct Node {
ll sum;
int l, r, mx1, mx2, cmx, tag;
} T[N << 2];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is N << 2 enough? I'm curious

@chihhsi chihhsi requested a review from harry900831 June 22, 2023 03:26
@chihhsi
Copy link
Author

chihhsi commented Jun 22, 2023

@harry900831 Waiting review

@harry900831 harry900831 linked an issue Jun 27, 2023 that may be closed by this pull request
Copy link
Contributor

@harry900831 harry900831 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/data_structure/image/segment_tree/segment_tree_beats/.......

src/data_structure/segment_tree_beats.md Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Show resolved Hide resolved

Segment Tree Beats(簡稱 STB)是北京大學的吉如一提出的概念,發表在《区间最值操作与历史最值问题》[^note-1]中。

STB 可以滿足下列兩項性質:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這邊用滿足下列兩項性質有點怪


考慮下面這一種解法:

對線段樹每一個節點除了維護區間和 \\( sum \\) 之外,還要額外維護區間中的最大值 \\( mx1 \\),嚴格次大值 \\( mx2 \\) 以及最大值個數 \\( cmx \\)。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

線段樹每一個節點除了維護區間和 \( sum \)、區間最大值 \( mx1 \) 之外,還要額外維護嚴格次大值 \( mx2 \) 以及最大值個數 \( cmx \)。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因此第二三種操作就是普通線段數直接更新就好
現在讓我們考慮第一種操作

Comment on lines 194 to 208
#### 歷史最大值

當前位置下曾經出現過的數的最大值。定義一個輔助數組 \\( B \\),最開始 \\( B \\) 數組與 \\( A \\) 數組完全相同。在每一次操作後,對每一個 \\( i \in [1, n] \\),我們都進行一次更新,讓 \\( B_i = max(B_i, A_i) \\)。這時,我們將 \\( B_i \\) 稱作 \\( i \\) 這個位置的歷史最大值。

<img src="image/segment_tree_beats/6-1.jpg" width="500" style="display:block; margin: 0 auto;"/>

#### 歷史最小值

當前位置下曾經出現過的數的最小值。定義一個輔助數組 \\( B \\),最開始 \\( B \\) 數組與 \\( A \\) 數組完全相同。在每一次操作後,對每一個 \\( i \in [1, n] \\),我們都進行一次更新,讓 \\( B_i = min(B_i, A_i) \\)。這時,我們將 \\( B_i \\) 稱作 \\( i \\) 這個位置的歷史最小值。

<img src="image/segment_tree_beats/6-2.jpg" width="500" style="display:block; margin: 0 auto;"/>

#### 歷史版本和

定義一個輔助數組 \\( B \\),最開始 \\( B \\) 數組中的所有數都是 \\( 0 \\)。在每一次操作後,對每一個 \\( i \in [1, n] \\),我們都進行一次更新,讓 \\( B_i \\) 加上 \\( A_i \\)。這時,我們將 \\( B_i \\) 稱作 \\( i \\) 這個位置的歷史版本和。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

沒有講解這些操作怎麼處理?

Comment on lines 212 to 221
> 例題 2. [Tyvj - CPU 監控](http://www.tyvj.cn/p/1518)
>
> 給一個長度為 \\( n \\) 的序列 \\( A \\),同時定義一個輔助數組 \\( B \\),\\( B \\)開始與 \\( A \\) 完全相同。接下來對其進行 \\( m \\) 筆操作,操作有四種:
>
> 1. 給定 \\( L, R, x \\),對所有 \\( i \in [L, R] \\),將 \\( A_i \\) 修改成 \\( x \\)。
> 2. 給定 \\( L, R, x \\),對所有 \\( i \in [L, R] \\),將 \\( A_i \\) 加上 \\( x \\)。
> 3. 給定 \\( L, R \\),對所有 \\( i \in [L, R] \\),輸出 \\( A_i \\) 的最大值。
> 4. 給定 \\( L, R \\),對所有 \\( i \in [L, R] \\),輸出 \\( B_i \\) 的最大值。
>
> - \\( n, m \leq 10^5 \\)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這題目敘述沒錯嗎?

沒有提到怎樣會更改到B啊


### 可以用懶標記處理的問題

> 例題 2. [Tyvj - CPU 監控](http://www.tyvj.cn/p/1518)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalid link

Tyvj 又是什麼 oj?

能使用常用oj 就勁量使用常用oj

src/data_structure/segment_tree_beats.md Show resolved Hide resolved

</details>

### 歷史最值問題
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這個章節需要重新organize

@harry900831
Copy link
Contributor

Please resolve the conversation if you fix it~

src/data_structure/segment_tree_beats.md Outdated Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Outdated Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Outdated Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Outdated Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Outdated Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Show resolved Hide resolved
src/data_structure/segment_tree_beats.md Outdated Show resolved Hide resolved

<img src="image/segment_tree/segment_tree_beats/3-2.gif" width="700" style="display:block; margin: 0 auto;"/>

最後讓區間 \\( [4, 4] \\) 的元素加上 \\( 1 \\)。階層為 \\( 2 \\) 的右邊節點下傳區間加標記,接著發現其左子節點的 \\( mx2 < 3 < mx1 \\),所以需要用 \\( 3 \\) 更新左子節點的最大值和最小值。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這部分看起來是會造成時間複雜度跟上一題不同的最大關鍵
但是單純看你上面的敘述想不到會這樣

@harry900831
Copy link
Contributor

@chihhsi Do you receive my discord message?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add chapter Segment Tree Beats
2 participants