[Tutorial] on a more ad hoc segment tree writing

On a more ad hoc segment tree writing

This level of NOIP blog is mainly to prevent writing after I lost AFO written (fog)

Foreword

Bloggers usually write segment tree is often used when writing a structured body fly pointers, such an approach has several advantages:

  • Clarity of writing is not easy to hang, and without the help of macros you can do this
  • Various segments of the tree can be achieved in a small modification on the basis of flexibility, such as:
    • Yes endurance of
    • Dynamic prescription
    • Segment tree merger
  • RE error will be reported using gdb class of tools to facilitate rapid positioning error (balanced tree can also be used similar wording, one second board positioning error)
  • The segment tree and function relatively ugly implicit part of the parameters passed, so(may)Some look more beautiful
  • In the case of dynamic memory pool rather than the memory array is generally higher than ordinary writing higher efficiency
  • Native integration, can be directly applied without making various modifications compatibility between nested data structure
  • As a member of the interface function appears, does not appear identifier conflict (same name) case

Take the segment tree implementation example of the most basic is the following: in \ (O (n + q \ log n) \) within the time complexity length \ (n-\) sequence is \ (Q \) sub-interval addition section summing an example to introduce such an approach.

Subject to full implementation of a road or other examples can refer to the code that came with my other blog post or submitted directly query I UOJ recording / LOJ's.

(My current wording may not do with the structure pointer + can do the most beautiful and the extent of not doing rigorous package, seeking dalao light spray)

Note that the focus of this article is written rather than the tree line knowledge qwq ...

Pre-skills to know to call a member function of an object when there is a thispointer to the source object calls this function.

definition

Definition of a structure Node. As the segment tree node variables and member functions of this structure is defined as follows:

struct Node{
    int l;
    int r;
    int add;
    int sum;
    Node* lch;
    Node* rch;
    Node(int,int);
    void Add(int);
    void Maintain();
    void PushDown();
    int Query(int,int);
    void Add(int,int,int);
};

among them:

  • lAnd rrepresent the current node represents a section of the left and right end points
  • add Lazy evaluation interval flag is in addition
  • sum It is the current interval and
  • lchAnd rchpointers are pointing to the current node's left child node
  • Node(int,int) It is the constructor for achievements
  • void Add(int d)It is a secondary function, the value interval represented by the current node are coupled in \ (D \) .
  • void Maintain() It is a function of the current node updates information of sub-nodes
  • void PushDown() Is a transfer function labeled lazy evaluation
  • int Query(int l,int r)Of interval \ ([l, r] \ ) summation
  • void Add(int l,int r,int d)Of interval \ ([l, r] \ ) values are plus \ (D \) .

Contribute

Contribution of individual generally selected constructor written as follows (where the initial value. \ (0 \) ):

Node(int l,int r):l(l),r(r),add(0),sum(0){
    if(l!=r){
        int mid=(l+r)>>1;
        this->lch=new Node(l,mid);
        this->rch=new Node(mid+1,r);
        this->Maintain(); // 因为初值为 0 所以此处可以不加
    }
}

This implementation method utilizes new Node()creates a new node and returns a pointer to the recursive nature of the establishment of a segment tree.

new Node(l,r)Actually create a section containing \ ([l, r] \ ) of the segment tree where \ (L \) and (R & lt \) \ ensuring \ (l \ le r \) may be a case where any.

I noticed in \ (l = r \) when not to lchand rchassignment, that is a field guide. Why leave this field guide will not be the problem? We do explain to the query.

When in actual use can do so:

int main(){
    Node* Tree=new Node(1,n);
}

Next you can create a section containing \ ([1, n] \ ) of the segment tree.

Interval addition

Modified in this example to be the \ (O (\ log n) \) interval adder in time complexity, the need to implement lazy evaluation, when the operation deep into the subtree when mass marker is calculated.

Lazy evaluation

First, to achieve a small helper function void Add(int):

void Add(int d){
    this->add+=d;
    this->sum+=(this->r-this->l+1)*d;
}

Interval role is to represent the current node plus \ (D \) . Obviously not explain the meaning.

With After this little helper can do no brains to write void PushDown():

void PushDown(){
    if(this->add!=0){
        this->lch->Add(this->add);
        this->rch->Add(this->add);
        this->add=0;
    }
}

These two functions of all this->because there is no duplication of identifiers can actually be removed, bloggers personal habits are reserved.

maintain

After modifying apparently ancestry tree knot point of information needs to be updated, so write:

void Maintain(){
    this->sum=this->lch->sum+this->rch->sum;
}

modify

The main operation of this function can be written:

void Add(int l,int r,int d){
    if(l<=this->l&&this->r<=r)
        this->Add(d);
    else{
        this->PushDown();
        if(l<=this->lch->r)
            this->lch->Add(l,r,d);
        if(this->rch->l<=r)
            this->rch->Add(l,r,d);
        this->Maintain();
    }
}

Sentenced to pay part of which is very well written no brain, but not the whole variety of \ (\ pm1 \) trouble.

Note that the first line this->l/ this->rand l/ rare different. this->l/ this->rRefers to "this" range segment tree represents, and l/ rrepresents the range you want to modify.

use

It can be used as such in the main functions:

int main(){
    Node* Tree=new Node(1,n);
    Tree->Add(l,r,d); // Add d to [l,r]
}

Interval sum

According to the partition of routine tree line, we only need to determine whether the summation interval is completely contained for the current segment, if fully contained directly returned, otherwise pass under lazy evaluation mark and partition it, and the sum of the sub-tree of recursive interval intersection sum following the partition of direct implementation of the process just described.

int Query(int l,int r){
    if(l<=this->l&&this->r<=r)
        return this->sum;
    else{
        int ans=0;
        this->PushDown();
        if(l<=this->lch->r)
            ans+=this->lch->Query(l,r);
        if(this->rch->l<=r)
            ans+=this->rch->Query(l,r);
        return ans;
    }
}

In fact, when queried, sometimes some special maintenance operations, such as matrix multiplication / largest sub-segment and a class of things. This time it may take a brain to know what ansthe initial value is valid. However, we actually directly below it kind of wording avoided the issue of temporary variables and the initial value of the unit cell:

int Query(int l,int r){
    if(l<=this->l&&this->r<=r)
        return this->sum;
    else{
        this->PushDown();
        if(r<=this->lch->r)
            return this->lch->Query(l,r);
        if(this->rch->l<=l)
            return this->rch->Query(l,r);
        return this->lch->Query(l,r)+this->rch->Query(l,r);
    }
}

Wherein the addition operation can be changed to meet any binding law.

Thus the main function can be used:

int main(){
    Node* Tree=new Node(1,n);
    Tree->Add(l,r,d); // Add d to [l,r]
    printf("%d\n",Tree->Query(l,r)); // Query sum of [l,r]
}

Yes endurance of

The following modifications to a single point section and request persistence summing an example will be described.

A constructor to implement the information copied to the original node:

Node(Node* ptr){
    *this=*ptr;
}

Then modifying each copied again when the first node on the bin simple without brain. (The following is achieved at index \ (X \) values into \ (D \) )

void Modify(int x,int d){
    if(this->l==this->r) //如果是叶子
        this->sum=d;
    else{
        if(x<=this->lch->r){
            this->lch=new Node(this->lch);
            this->lch->Modify(x,d);
        }
        else{
            this->rch=new Node(this->rch);
            this->rch->Modify(x,d);
        }
        this->Maintain();
    }
}

In fact, in the case of a single point can also be an expression with a question mark (? Or ternary operator how casually called) engage in a practice:

void Modify(int x,int d){
    if(this->l==this->r) //如果是叶子
        this->sum=d;
    else{
        (x<=this->lch->r?
         this->lch=new Node(this->lch):
         this->rch=new Node(this->rch)
        )->Modify(x,d);
        this->Maintain();
    }
}

Dynamic prescription

Dynamic open point of time we can not just keep the wild pointer because we need to be judged by the sentence null pointer of the current sub-tree has not been established.

So we changed this constructor:

Node(int l,int r):l(l),r(r),add(0),sum(0),lch(NULL),rch(NULL){}

Then you need to pay attention everywhere sentenced empty, because this can not be assumed as long as the current point is not a leaf node can secure access to the child.

If you encounter an empty node requirements and then ignored, if you need to enter the sub-tree operation, then on New.

And in judging whether and child nodes intersect, it also can not refer directly to the endpoint information child nodes, and there may need to be calculated int mid=(this->l+this->r)>>1. When general inquiries not counted necessary, because the discovery node is then empty is not required and it sentenced to pay.

Memory pool

Sometimes dynamic memory allocation may cause a few performance issues, if the minor can try to use the card often memory pool.

Memory pool, which means that the beginning of the last and then allocate a large lump.

The method is to first open a memory and a tail pointer, POOL_SIZEthe maximum number of nodes to be used:

Node Pool[POOL_SIZE]
Node* PTop=Pool;

Then all newreplaced new(PTop++)it. new(ptr)The meaning is pretending to ptrpoint to the new memory is allocated, and then call the constructor and returns the pointer.

defect

Obviously, this wording also has some flaws, there are currently found in the following points:

  • Because the pointer can not be obtained quickly LCA bit operation position or \ (K \) position of the stage is then run fast ancestor zkw inferior segment tree.
  • To store endpoint because it is left relatively large memory overhead in the node, but may be finished by post this->l/ this->rreplace thisl/ thisrdo slightly modified as a parameter can be alleviated.
  • Write a long look, but in fact if you write a function inside the structure rather than the prior statement, and redundant this->remove if not much longer (after all parameters travels less ah feed).

Last I hope interested readers can try to realize what such an approach, Should you notice this stuff is really good with it?

(Cheeky seeking recommendations)

Guess you like

Origin www.cnblogs.com/rvalue/p/11028820.html