Блог пользователя Aslan_Aslanli

Автор Aslan_Aslanli, история, 5 месяцев назад, По-английски

Hello. I was wondering why we always make a segment tree's length 4*n. So, I went ahead and tried to prove it, and in the end, I was able to prove that the number of nodes is exactly 2*n-1. So, I want to share it.

Now, first of all, let's initially prove why it works when n is a power of 2, so let's say that n=2^k. We construct the tree (I am gonna put it's picture too), and we can see that there are n leaves.

Because each two leaves combine to make another parent (node), we can say that those leaves have n/2 parents, those parents have n/4 parents, n/8, n/16, and at the end, there is only a single node left, which is our initial segment (1->n). Now, we can use simple math in order to calculate the number of nodes. Let's say that the number of nodes is S. We can see that S=n + n/2 + n/4 + n/8 + ...... + 1, which is a geometric progression where the a=n, the number of element which we sum up is k+1 (because n is 2^k), and the r=1/2. Using the geometric progression sum formula, we get S=(n*(1-((1/2)^(k+1))))/(1-1/2), and because n=2^k, S=(2^k-(1/2))/(1/2), and finally S=2^(k+1)-1. Once again, because n=2^k, S=2*n-1.

Okay, now that we got that out of the way, why is that formula correct for any given number n? Let's say n=2^k + h where k=log2(n). That means that h<2^k. Now, when we construct the array this way, we can see one very important thing. I am going to explain it with an example, and I am going to put a picture about it (please note that in the picture, instead of using a segment for each node, I am gonna use the length of that segment).

So let's look at the examples 8 and 9. Notice how when we increase 8 by one, up until the last leave, the overall number of nodes do not change. It becomes even more clear when we look at the example with 8 and 10, in which the overall number of nodes do not change until the last moment, even though the length of the segment covered by that node do change.

And finally, we can see that each added number adds two additional nodes to the overall tree, which means that S = 2^(k+1) — 1 + 2*h, S = 2^(k+1) + 2*h — 1, S = 2 * (2^k + h) — 1. And because n=2^k+h, S = 2*n-1.

Now, the problem is that when you do make the seg tree recursively, you kinda look at "Ghost nodes", so in recursive implementation, you cannot set the length of the segment tree anything lower than 4*n. BUUUUUTTTT, if you were to implement it iteratively, you would be able to set the length 2*n, which would save you some memory. This is mainly used in international olimpiads where the memory limit is too tight to set it 4*n.

I hope that this was useful, and although it is not a rare find (A lot of people are aware of this), I just wanted to show the proof I did and maybe some people can benefit from this blog too. Cya :)

  • Проголосовать: нравится
  • +26
  • Проголосовать: не нравится

»
5 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by Aslan_Aslanli (previous revision, new revision, compare).

»
5 месяцев назад, скрыть # |
 
Проголосовать: нравится +1 Проголосовать: не нравится

Setting dfs order fills the gap and reduces the memory to 2n-1.

»
5 месяцев назад, скрыть # |
 
Проголосовать: нравится +1 Проголосовать: не нравится
»
5 месяцев назад, скрыть # |
 
Проголосовать: нравится +11 Проголосовать: не нравится

A simpler proof: First consider the base case n = 1. The segment tree has exactly one node, so the formula holds. Whenever n increases by 1, one leaf node in the segment tree gains two children, increasing the total number of nodes by 2.

»
5 месяцев назад, скрыть # |
Rev. 2  
Проголосовать: нравится 0 Проголосовать: не нравится

Where did the 4*MAXN thing even come from? I'm guessing people extending the array to next power of 2 because "easier implementation"?

  • »
    »
    5 месяцев назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    Nah 4*n is necessary for recursive approach since we are moving top to bottom, since blocks can be on different level if it is not a perfect power of 2, u end up doing 2*index,2*index+1 for indexes that didnt need it(we already got to leaf on that side) but since other side was pending it went deeper, this gives segmentation fault since those indexes dont exist on one side, so we have to create dummy nodes aka 4*n to ensure it is a perfect binary tree and we dont go out of bound.

    Iteration works bottom up, so we guarentee to start from the leaves and end at the top hence assigning 2*n doesnt make it out of bound since we are never accessing out of bound index at any level.

»
5 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

We can use recursive seg tree with just $$$2*n-1$$$ nodes allocated! This is possible because we know the size of lower levels, so if we are now in a level covering range $$$[l,r]$$$ in index $$$i$$$, the interval in the node vector $$$[i,i+2*(r-l+1)-1]$$$ represents this subtree. To do that, if the middle point is $$$mid$$$, then the left subtree would be in the range $$$[i+1,i+2*(mid-l+1)-1]$$$ and the right subtree would be in the range $$$[i+2*(mid-l+1),i+2*(r-l+1)-1]$$$. The way of accssing is something like:

seg(i, l, r)
  mid = l + (r-l)/2;
  seg(i+1,l,mid) // left node
  seg(i+2*(mid-l+1),mid+1,r) // right node

You can also see this in cp-algorithms: https://cp-algorithms.com/data_structures/segment_tree.html#memory-efficient-implementation

»
5 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Set node index to (l+r) | (l!=r), then only 2*n length of array is enough. This is kinda weird but somehow correct. Also this way only leaves will have even indexes.

int query(int l,int r)
{
    int cur=l+r|l!=r;
    ......
    return a[cur].num;
}
  • »
    »
    5 месяцев назад, скрыть # ^ |
    Rev. 2  
    Проголосовать: нравится +3 Проголосовать: не нравится

    correctness:

    • a segment tree has n leaves and n-1 internal nodes.
    • each internal node has a midpoint (equal to (l+r)/2)
    • these n-1 midpoints are unique and lie in range [0,n-2]
    • let's map these n-1 unique midpoints to odd indexes: f(l,r) -> (l+r)/2*2+1 = (l+r)|1
    • The n leaf nodes have range l==r, 0<=l<=n-1.
    • Let's map the n leaves to even indexes: f(l,r) -> 2l = l+r = (l+r)|0
    • combine the 2 cases: f(l,r) -> (l+r)|(l!=r)

    edit: I wanted to point out you can do 3n lazy seg with the following mapping:

    f(l,r) -> l==r ? n-1+l : (l+r)/2