Simple introduction to line segment tree

Keywords: data structure

catalogue

1, Introduction to segment tree

1. Import segment tree

2. Common sense of segment tree

2, Operation of segment tree

         1. Build a tree

2. Interval query

3. Interval modification

3, Lazy tag

1. The role of lazy markers

2. Code

4, P3372 [template] segment tree 1   Solution to the problem

1, Introduction to segment tree

1. Import segment tree

As shown in the question, a sequence is known, and X is read in each operation:

        X==1 add every number in [L,R] interval   k.

        X==2 find the sum of each number in a certain interval.

   This is P3372 in Los Angeles , before you learn segment trees, there are two simple ways:

Method 1Method 2
nameviolencePrefix and
Interval plus k1R-L+1
Interval summationR-L+11

Obviously, one operation of these two methods will take a lot of time, so we need a data structure that can make the two operations take about the same time - segment tree.

2. Common sense of segment tree

  Each node of the segment tree is like this:

Where p represents the node number, L,R represents the subscript, and dat represents the interval sum of [L,R]. For convenience, from the node number, the segment tree is actually similar to a heap.

According to the above, the seven numbers 2 5 1 6 4 7 3 are saved into a segment tree, and the length is as follows:

A line segment tree with n nodes is a tree with a depth of(k for short), there are about n nodes in the k-1 layer, so the first k-1 layer is  There are nodes in layer kTwo nodes, so×, about 4n. If you want to save space, you need to use a pointer. The storage of ordinary segment tree is:

#define ll long long
struct node
{
    int p,l,r;
    ll dat;//Open long long
}a[N<<2];//Quadruple space

2, Operation of segment tree

1. Build a tree

The dat of the segment tree node is the sum of the dats of the left and right nodes. Therefore, we must first find the following DAT, stop when it reaches the leaf node, and then upload the value. Therefore, the establishment of the segment tree is realized recursively.

void build(int p,int l,int r)
{
    a[p].l=l;
    a[p].r=r;
    if(l==r)//Leaf node
    {
        a[p].dat=t[l];//t[l] represents the L-th element of the original array
        return ;//Stop downward access
    }
    int mid=(l+r)>>1;
    build(p<<1,l,mid);//Build left subtree
    build(p<<1|1,mid+1,r);//Construct right subtree
    a[p].dat=a[p<<1].dat+a[p<<1|1].dat;//Backtracking value
}

2. Interval query

Combined with the above example, let's query the interval sum of [3,5].

If we add the leaf nodes in all intervals, the time will become very large. In fact, this interval sum can also be composed of some cells.

  Red indicates the final answer range, and blue indicates the query path.

Query steps:

         1. If the query interval completely contains this interval, it must be the answer interval and return dat.

        2. If it contains a part, it recurses downward to the corresponding part.

        3. Every two intervals are completely included or not included, so there will be no repeated accumulation of some part of the answer.

ll query(int p,int l,int r)//Some questions need to be long long
{
    if(l<=a[p].l&&r>=a[p].r) return a[p].dat;//Complete inclusion, answer interval
    int mid=(a[p].l+a[p].r)>>1;
    ll sum=0;
    if(l<=mid) sum+=query(p<<1,l,r);//Look to the left
    if(r>mid) sum+=query(p<<1|1,l,r);//Check to the right
    return sum;
}

  Note: the judgment of left and right recursion is two if, because one of the two intervals may contain a small part of the answer interval.

3. Interval modification

We need to modify the [2,5] interval (add 2 to each value).

This time, we must find and modify the leaf node in each interval, because if you only modify the interval, if you continue recursion after reaching that interval during a query, the interval will explode to 0.

  The blue part has not only been modified.

Modification steps:

        1. Continue recursion until the leaf node contains some or all of them.

  Note: as above, the left and right recursive judgment is two if, because one of the two intervals may contain a small part of the answer interval.

3, Lazy tag

1. The role of lazy markers

At this time, the segment tree is not enough to solve this problem, because it takes a long time to access the root node during interval modification, so we import lazy tags into the segment tree.

  • modify

        1. If it is included directly or partially, add (R-L+1) to the value of the node directly × v. The lazy flag of the node is added with V, and the lazy flag of the node is passed down to the left and right subtrees, and then the lazy flag is cleared to 0 and backtracked.

  • query

         1. Because the interval is directly changed during modification, and the value is only passed to the left and right subtrees. When the current node recurses, it is passed to the left and right subtrees. Therefore, it is possible to add what should be added during access, which greatly reduces the time.

2. Code

//There is no change in the establishment process
void spread(int p)//Lazy Flag 
{
    if(a[p].lazy)
    {
        a[p<<1].dat+=a[p].lazy*(a[p<<1].r-a[p<<1].l+1);
        a[p<<1].lazy+=a[p].lazy;
        a[p<<1|1].dat+=a[p].lazy*(a[p<<1|1].r-a[p<<1|1].l+1);
        a[p<<1|1].lazy+=a[p].lazy;
        a[p].lazy=0;
    }
}
ll query(int p,int l,int r)
{
    if(l<=a[p].l&&r>=a[p].r) return a[p].dat;

    spread(p);//Downlink lazy flag

    int mid=(a[p].l+a[p].r)>>1;
    ll sum=0;
    if(l<=mid) sum+=query(p<<1,l,r);
    if(r>mid) sum+=query(p<<1|1,l,r);
    return sum;
}
void update(int p,int l,int r,int v)
{
    if(l<=a[p].l&&r>=a[p].r)
    {
        a[p].dat+=(long long)v*(a[p].r-a[p].l+1);//Direct interval modification
        a[p].lazy+=v;//Modify lazy tag
        return;
    }

    spread(p);//Downlink lazy flag

    int mid=(a[p].l+a[p].r)>>1;
    if(l<=mid) update(p<<1,l,r,v);
    if(r>mid) update(p<<1|1,l,r,v);
    a[p].dat=a[p<<1].dat+a[p<<1|1].dat;
}

4, P3372 [template] segment tree 1   Solution to the problem

#include <bits/stdc++.h>
#define N 100005
#define ll long long
struct node
{
    int p,l,r;
    ll dat,lazy;
}a[N<<2];
int t[N];
void build(int p,int l,int r)
{
    a[p].l=l;
    a[p].r=r;
    if(l==r)
    {
        a[p].dat=t[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    a[p].dat=a[p<<1].dat+a[p<<1|1].dat;
}
void spread(int p)
{
    if(a[p].lazy)
    {
        a[p<<1].dat+=a[p].lazy*(a[p<<1].r-a[p<<1].l+1);
        a[p<<1].lazy+=a[p].lazy;
        a[p<<1|1].dat+=a[p].lazy*(a[p<<1|1].r-a[p<<1|1].l+1);
        a[p<<1|1].lazy+=a[p].lazy;
        a[p].lazy=0;
    }
}
ll query(int p,int l,int r)
{
    if(l<=a[p].l&&r>=a[p].r) return a[p].dat;
    spread(p);
    int mid=(a[p].l+a[p].r)>>1;
    ll sum=0;
    if(l<=mid) sum+=query(p<<1,l,r);
    if(r>mid) sum+=query(p<<1|1,l,r);
    return sum;
}
void update(int p,int l,int r,int v)
{
    if(l<=a[p].l&&r>=a[p].r)
    {
        a[p].dat+=(long long)v*(a[p].r-a[p].l+1);
        a[p].lazy+=v;
        return;
    }
    spread(p);
    int mid=(a[p].l+a[p].r)>>1;
    if(l<=mid) update(p<<1,l,r,v);
    if(r>mid) update(p<<1|1,l,r,v);
    a[p].dat=a[p<<1].dat+a[p<<1|1].dat;
}
int main()
{
	int i,m,n,x,y,z,k;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++) scanf("%d",&t[i]);
	build(1,1,n);
	while(m--)
	{
		scanf("%d",&k);
		if(k==1)
		{
			scanf("%d%d%d",&x,&y,&z);
			update(1,x,y,z);
		}
		else
		{
			scanf("%d%d",&x,&y);
			printf("%lld\n",query(1,x,y));
		}
	}
}

My first blog.

Posted by Goins on Fri, 01 Oct 2021 16:21:57 -0700