Segment Tree is a data structure commonly used in competitive programming to maintain interval information.
Segment Tree can perform operations such as single-point modification, interval modification, interval query (interval sum, interval maximum, interval minimum) in \(O(\log N)\) time complexity.
Basic Structure and Construction of Segment Tree
Process
Segment Tree recursively divides each interval of length not equal to \(1\) into left and right intervals, partitioning the entire segment into a tree structure, and obtains the information of the interval by merging the information of the left and right intervals. This data structure conveniently supports most interval operations.
Consider an array \(a=\{10,11,12,13,14\}\) of size \(5\). To convert it into a segment tree, we can do the following: Let the root node of the segment tree be numbered \(1\), and use array \(d\) to store our segment tree, where \(d_i\) stores the value of the node numbered \(i\) in the segment tree (here, the value maintained by each node is the sum of the interval represented by that node).
We first show the structure of this segment tree, as illustrated:
In the figure, the intervals marked in red within each node indicate the position interval in array \(a\) that the node governs. For example, the interval governed by \(d_1\) is \([1,5]\) (\(a_1,a_2, \cdots ,a_5\)), meaning the value stored in \(d_1\) is \(a_1+a_2+ \cdots +a_5\), and \(d_1=60\) indicates \(a_1+a_2+ \cdots +a_5=60\).
By observation, it is not difficult to see that the left child of \(d_i\) is \(d_{2\times i}\), and the right child of \(d_i\) is \(d_{2\times i+1}\). If \(d_i\) represents interval \([s,t]\) (i.e., \(d_i=a_s+a_{s+1}+ \cdots +a_t\)), then the left child of \(d_i\) represents interval \([ s, \frac{s+t}{2} ]\), and the right child of \(d_i\) represents interval \([ \frac{s+t}{2} +1,t ]\).
In implementation, we consider recursive construction. Let the current root node be \(p\). If the length of the interval governed by the root node is already \(1\), we can directly initialize the node based on the value at the corresponding position in array \(a\). Otherwise, we split the interval at the midpoint into two subintervals, recursively construct the tree for the left and right child nodes, and finally merge the information of the two child nodes.
Implementation
Here is the code implementation, with comments for understanding:
1 2 3 4 5 6 7 8 910111213
voidbuild(ints,intt,intp){// Build segment tree for interval [s,t], current root number is pif(s==t){d[p]=a[s];return;}intm=s+((t-s)>>1);// The precedence of shift operators is lower than addition/subtraction, so parentheses are added// If written as (s + t) >> 1, it might exceed int rangebuild(s,m,p*2),build(m+1,t,p*2+1);// Recursively build tree for left and right intervalsd[p]=d[p*2]+d[(p*2)+1];}
1 2 3 4 5 6 7 8 9101112
defbuild(s,t,p):# Build segment tree for interval [s,t], current root number is pifs==t:d[p]=a[s]returnm=s+((t-s)>>1)# The precedence of shift operators is lower than addition/subtraction, so parentheses are added# If written as (s + t) >> 1, it might exceed int rangebuild(s,m,p*2)build(m+1,t,p*2+1)# Recursively build tree for left and right intervalsd[p]=d[p*2]+d[(p*2)+1]
Regarding segment tree space: If using heap storage (where \(2p\) is the left child of \(p\), and \(2p+1\) is the right child of \(p\)), if there are \(n\) leaf nodes, the maximum range of array d is \(2^{\left\lceil\log{n}\right\rceil+1}\).
Analysis: It is easy to know that the depth of the segment tree is \(\left\lceil\log{n}\right\rceil\). In heap storage, the number of leaf nodes (including unused leaf nodes) is \(2^{\left\lceil\log{n}\right\rceil}\). Since it is a complete binary tree, the total number of nodes is \(2^{\left\lceil\log{n}\right\rceil+1}-1\). Of course, if you prefer not to calculate, you can directly set the array length to \(4n\), because the maximum value of \(\frac{2^{\left\lceil\log{n}\right\rceil+1}-1}{n}\) is reached when \(n=2^{x}+1(x\in N_{+})\), where the number of nodes is \(2^{\left\lceil\log{n}\right\rceil+1}-1=2^{x+2}-1=4n-5\).
Heap storage has unused leaf nodes. Consider using a memory pool to manage segment tree nodes, obtaining a new node from the pool whenever needed. Considering from bottom up, every two bottom nodes merge into one upper node. Therefore, it can be proven similarly to Huffman tree that if there are \(n\) leaf nodes, such a segment tree has a total of \(2n-1\) nodes. Its space efficiency is better than heap storage and is possibly optimal.
Interval queries, such as finding the sum of interval \([l,r]\) (i.e., \(a_l+a_{l+1}+ \cdots +a_r\)), finding interval maximum/minimum, etc.
Still using the initial figure as an example, if we want to query the sum of interval \([1,5]\), we can directly get the value of \(d_1\) (\(60\)).
If the query interval is \([3,5]\), we cannot directly get the interval value. However, \([3,5]\) can be split into \([3,3]\) and \([4,5]\). The answer for this interval can be obtained by merging the answers of these two intervals.
Generally, if the query interval is \([l,r]\), it can be split into at most \(O(\log n)\)maximal intervals. Merging these intervals gives the answer for \([l,r]\).
Implementation
Here is the code implementation, with comments for understanding:
1 2 3 4 5 6 7 8 91011
intgetsum(intl,intr,ints,intt,intp){// [l, r] is the query interval, [s, t] is the interval contained by the current node, p is the current node numberif(l<=s&&t<=r)returnd[p];// When the current interval is a subset of the query interval, directly return the sum of the current intervalintm=s+((t-s)>>1),sum=0;if(l<=m)sum+=getsum(l,r,s,m,p*2);// If the left child's interval [s, m] intersects with the query interval, recursively query the left childif(r>m)sum+=getsum(l,r,m+1,t,p*2+1);// If the right child's interval [m + 1, t] intersects with the query interval, recursively query the right childreturnsum;}
1 2 3 4 5 6 7 8 910111213
defgetsum(l,r,s,t,p):# [l, r] is the query interval, [s, t] is the interval contained by the current node, p is the current node numberifl<=sandt<=r:returnd[p]# When the current interval is a subset of the query interval, directly return the sum of the current intervalm=s+((t-s)>>1)sum=0ifl<=m:sum=sum+getsum(l,r,s,m,p*2)# If the left child's interval [s, m] intersects with the query interval, recursively query the left childifr>m:sum=sum+getsum(l,r,m+1,t,p*2+1)# If the right child's interval [m + 1, t] intersects with the query interval, recursively query the right childreturnsum
Interval Modification and Lazy Propagation in Segment Tree
Process
If we want to modify interval \([l,r]\), traversing and modifying all nodes that are contained in interval \([l,r]\) would be unacceptably time-consuming. Here we introduce something called "lazy propagation".
Lazy propagation, simply put, delays changes to node information, thereby reducing potentially unnecessary operations. Each time a modification is performed, we mark the node to indicate that the corresponding interval was changed in some operation, but we do not update the information of the node's children. The actual modification is performed only the next time a node with a mark is accessed.
Still using the initial figure as an example, we will perform several operations of adding a value to each number in an interval. We now add a \(t_i\) to each node, representing the mark value carried by that node.
The initial situation is as follows (to save space, the interval governed by each node is not shown):
Now we want to add \(5\) to each number on \([3,5]\). Based on previous interval query experience, we quickly find two maximal intervals \([3,3]\) and \([4,5]\) (corresponding to nodes \(5\) and \(3\) in the segment tree, respectively).
We directly modify these two nodes and mark them:
We notice that although the information of node \(3\) is modified (since this interval governs two numbers, the value added to \(d_3\) is \(5 \times 2=10\)), its two child nodes are not yet updated and still retain the information before modification. However, don't worry. Although the modification hasn't been performed yet, when we need to query the information of these two child nodes, we will use the mark to modify their information, ensuring the query result remains accurate.
Next, we query the sum of numbers in interval \([4,4]\).
We recursively find interval \([4,5]\), but this interval is not our target interval, and there is still a mark on this interval. Now it's time to propagate the mark. We update the information of the two child intervals of this interval and clear the mark on this interval.
Now the values of nodes \(6\) and \(7\) become the latest values, and the query result is accurate.
Implementation
Next, we provide reference implementations for interval modification and query operations in the presence of marks.
Interval modification (adding a value to an interval):
1 2 3 4 5 6 7 8 910111213141516171819
// [l, r] is the modification interval, c is the change amount of the modified elements, [s, t] is the interval contained by the current node, p// is the current node numbervoidupdate(intl,intr,intc,ints,intt,intp){// When the current interval is a subset of the modification interval, directly modify the current node's value, then mark, end modificationif(l<=s&&t<=r){d[p]+=(t-s+1)*c,b[p]+=c;return;}intm=s+((t-s)>>1);if(b[p]&&s!=t){// If the current node's lazy mark is non-empty, update the values and lazy marks of the current node's two child nodesd[p*2]+=b[p]*(m-s+1),d[p*2+1]+=b[p]*(t-m);b[p*2]+=b[p],b[p*2+1]+=b[p];// Pass the mark down to child nodesb[p]=0;// Clear the current node's mark}if(l<=m)update(l,r,c,s,m,p*2);if(r>m)update(l,r,c,m+1,t,p*2+1);d[p]=d[p*2]+d[p*2+1];}
1 2 3 4 5 6 7 8 91011121314151617181920212223
defupdate(l,r,c,s,t,p):# [l, r] is the modification interval, c is the change amount of the modified elements, [s, t] is the interval contained by the current node, p# is the current node numberifl<=sandt<=r:d[p]=d[p]+(t-s+1)*cb[p]=b[p]+creturn# When the current interval is a subset of the modification interval, directly modify the current node's value, then mark, end modificationm=s+((t-s)>>1)ifb[p]ands!=t:# If the current node's lazy mark is non-empty, update the values and lazy marks of the current node's two child nodesd[p*2]=d[p*2]+b[p]*(m-s+1)d[p*2+1]=d[p*2+1]+b[p]*(t-m)# Pass the mark down to child nodesb[p*2]=b[p*2]+b[p]b[p*2+1]=b[p*2+1]+b[p]# Clear the current node's markb[p]=0ifl<=m:update(l,r,c,s,m,p*2)ifr>m:update(l,r,c,m+1,t,p*2+1)d[p]=d[p*2]+d[p*2+1]
Interval query (interval sum):
1 2 3 4 5 6 7 8 910111213141516
intgetsum(intl,intr,ints,intt,intp){// [l, r] is the query interval, [s, t] is the interval contained by the current node, p is the current node numberif(l<=s&&t<=r)returnd[p];// When the current interval is a subset of the query interval, directly return the sum of the current intervalintm=s+((t-s)>>1);if(b[p]){// If the current node's lazy mark is non-empty, update the values and lazy marks of the current node's two child nodesd[p*2]+=b[p]*(m-s+1),d[p*2+1]+=b[p]*(t-m);b[p*2]+=b[p],b[p*2+1]+=b[p];// Pass the mark down to child nodesb[p]=0;// Clear the current node's mark}intsum=0;if(l<=m)sum=getsum(l,r,s,m,p*2);if(r>m)sum+=getsum(l,r,m+1,t,p*2+1);returnsum;}
1 2 3 4 5 6 7 8 9101112131415161718192021
defgetsum(l,r,s,t,p):# [l, r] is the query interval, [s, t] is the interval contained by the current node, p is the current node numberifl<=sandt<=r:returnd[p]# When the current interval is a subset of the query interval, directly return the sum of the current intervalm=s+((t-s)>>1)ifb[p]:# If the current node's lazy mark is non-empty, update the values and lazy marks of the current node's two child nodesd[p*2]=d[p*2]+b[p]*(m-s+1)d[p*2+1]=d[p*2+1]+b[p]*(t-m)# Pass the mark down to child nodesb[p*2]=b[p*2]+b[p]b[p*2+1]=b[p*2+1]+b[p]# Clear the current node's markb[p]=0sum=0ifl<=m:sum=getsum(l,r,s,m,p*2)ifr>m:sum=sum+getsum(l,r,m+1,t,p*2+1)returnsum
If you want to implement interval modification to a specific value rather than adding a value, the code is as follows:
voidupdate(intl,intr,intc,ints,intt,intp){if(l<=s&&t<=r){d[p]=(t-s+1)*c,b[p]=c,v[p]=1;return;}intm=s+((t-s)>>1);// Additional array to store whether the value is modifiedif(v[p]){d[p*2]=b[p]*(m-s+1),d[p*2+1]=b[p]*(t-m);b[p*2]=b[p*2+1]=b[p];v[p*2]=v[p*2+1]=1;v[p]=0;}if(l<=m)update(l,r,c,s,m,p*2);if(r>m)update(l,r,c,m+1,t,p*2+1);d[p]=d[p*2]+d[p*2+1];}intgetsum(intl,intr,ints,intt,intp){if(l<=s&&t<=r)returnd[p];intm=s+((t-s)>>1);if(v[p]){d[p*2]=b[p]*(m-s+1),d[p*2+1]=b[p]*(t-m);b[p*2]=b[p*2+1]=b[p];v[p*2]=v[p*2+1]=1;v[p]=0;}intsum=0;if(l<=m)sum=getsum(l,r,s,m,p*2);if(r>m)sum+=getsum(l,r,m+1,t,p*2+1);returnsum;}
As mentioned earlier, under heap storage, we need to allocate an array of size \(4n\) for the segment tree. To save space, we can avoid building the entire tree at once. Instead, we initially only create a root node representing the entire interval. When we need to access a subinterval, we then create the child node representing that interval. In this approach, we no longer use \(2p\) and \(2p+1\) to represent the children of node \(p\), but use \(\text{ls}\) and \(\text{rs}\) to record the child numbers. In summary, the core idea of dynamic segment tree is: nodes are created only when needed.
The time complexity of a single operation remains unchanged at \(O(\log n)\). Since each operation may create and access a completely new series of nodes, the number of nodes after \(m\) single-point operations is \(O(m\log n)\). At most, only \(2n-1\) nodes are needed, with no waste.
Single-point modification:
1 2 3 4 5 6 7 8 9101112131415161718
// root represents the root node of the entire segment tree; cnt represents the current number of nodesintn,cnt,root;intsum[n*2],ls[n*2],rs[n*2];// Usage: update(root, 1, n, x, f); where x is the number of the node to be modifiedvoidupdate(int&p,ints,intt,intx,intf){// Pass by referenceif(!p)p=++cnt;// When the node is empty, create a new nodeif(s==t){sum[p]+=f;return;}intm=s+((t-s)>>1);if(x<=m)update(ls[p],s,m,x,f);elseupdate(rs[p],m+1,t,x,f);sum[p]=sum[ls[p]]+sum[rs[p]];// pushup}
Interval query:
123456789
// Usage: query(root, 1, n, l, r);intquery(intp,ints,intt,intl,intr){if(!p)return0;// If the node is empty, return 0if(s>=l&&t<=r)returnsum[p];intm=s+((t-s)>>1),ans=0;if(l<=m)ans+=query(ls[p],s,m,l,r);if(r>m)ans+=query(rs[p],m+1,t,l,r);returnans;}
Interval modification is similar, but when propagating marks, be careful to create a new child if one is missing. Alternatively, use the technique of permanent marks.
Some Optimizations
Here are some optimizations for segment trees:
At leaf nodes, there is no need to propagate lazy marks, so lazy marks can be not propagated to leaf nodes.
Propagating lazy marks can be written as a dedicated function pushdown, and updating the current node from child nodes can be written as a dedicated function maintain (or symmetrically pushup), reducing code complexity.
Permanent marks: If it is certain that lazy marks will not overflow (i.e., exceed the maximum range representable by the data type) during the process, then marks can be made permanent. Permanent marks avoid propagating lazy marks; only when querying, the effect of the mark is added to the answer, thereby reducing constant factors. The specific handling depends on the problem characteristics and must be written in conjunction with the problem. This is also a technique used in segment tree of segment trees and persistent data structures.
C++ Templates
SegTreeLazyRangeAdd Segment Tree Template for Interval Addition/Sum
#include<iostream>usingLL=longlong;LLn,a[100005],d[270000],b[270000];voidbuild(LLl,LLr,LLp){// l: interval left endpoint r: interval right endpoint p: node indexif(l==r){d[p]=a[l];// assign value to the nodereturn;}LLm=l+((r-l)>>1);build(l,m,p<<1),build(m+1,r,(p<<1)|1);// build left and right subtrees respectivelyd[p]=d[p<<1]+d[(p<<1)|1];}voidupdate(LLl,LLr,LLc,LLs,LLt,LLp){if(l<=s&&t<=r){d[p]+=(t-s+1)*c,b[p]+=c;// if the interval is fully covered, directly compute the answerreturn;}LLm=s+((t-s)>>1);if(b[p])d[p<<1]+=b[p]*(m-s+1),d[(p<<1)|1]+=b[p]*(t-m),b[p<<1]+=b[p],b[(p<<1)|1]+=b[p];b[p]=0;if(l<=m)update(l,r,c,s,m,p<<1);// this line and the next line update nodes p*2 and p*2+1if(r>m)update(l,r,c,m+1,t,(p<<1)|1);d[p]=d[p<<1]+d[(p<<1)|1];// compute the interval sum for this node}LLgetsum(LLl,LLr,LLs,LLt,LLp){if(l<=s&&t<=r)returnd[p];LLm=s+((t-s)>>1);if(b[p])d[p<<1]+=b[p]*(m-s+1),d[(p<<1)|1]+=b[p]*(t-m),b[p<<1]+=b[p],b[(p<<1)|1]+=b[p];b[p]=0;LLsum=0;if(l<=m)sum=getsum(l,r,s,m,p<<1);// this line and the next line update answers for p*2 and p*2+1if(r>m)sum+=getsum(l,r,m+1,t,(p<<1)|1);returnsum;}intmain(){std::ios::sync_with_stdio(false);LLq,i1,i2,i3,i4;std::cin>>n>>q;for(LLi=1;i<=n;i++)std::cin>>a[i];build(1,n,1);while(q--){std::cin>>i1>>i2>>i3;if(i1==2)std::cout<<getsum(i2,i3,1,n,1)<<std::endl;// directly call the operation functionelsestd::cin>>i4,update(i2,i3,i4,1,n,1);}return0;}
#include<iostream>usingll=longlong;intn,m;llmod;lla[100005],sum[400005],mul[400005],laz[400005];voidup(inti){sum[i]=(sum[(i<<1)]+sum[(i<<1)|1])%mod;}voidpd(inti,ints,intt){intl=(i<<1),r=(i<<1)|1,mid=(s+t)>>1;if(mul[i]!=1){// Lazy tag propagation, two lazy tagsmul[l]*=mul[i];mul[l]%=mod;mul[r]*=mul[i];mul[r]%=mod;laz[l]*=mul[i];laz[l]%=mod;laz[r]*=mul[i];laz[r]%=mod;sum[l]*=mul[i];sum[l]%=mod;sum[r]*=mul[i];sum[r]%=mod;mul[i]=1;}if(laz[i]){// Lazy tag propagationsum[l]+=laz[i]*(mid-s+1);sum[l]%=mod;sum[r]+=laz[i]*(t-mid);sum[r]%=mod;laz[l]+=laz[i];laz[l]%=mod;laz[r]+=laz[i];laz[r]%=mod;laz[i]=0;}return;}voidbuild(ints,intt,inti){mul[i]=1;if(s==t){sum[i]=a[s];return;}intmid=s+((t-s)>>1);build(s,mid,i<<1);// Build the treebuild(mid+1,t,(i<<1)|1);up(i);}voidchen(intl,intr,ints,intt,inti,llz){intmid=s+((t-s)>>1);if(l<=s&&t<=r){mul[i]*=z;mul[i]%=mod;// This is modulolaz[i]*=z;laz[i]%=mod;// This is modulosum[i]*=z;sum[i]%=mod;// This is moduloreturn;}pd(i,s,t);if(mid>=l)chen(l,r,s,mid,(i<<1),z);if(mid+1<=r)chen(l,r,mid+1,t,(i<<1)|1,z);up(i);}voidadd(intl,intr,ints,intt,inti,llz){intmid=s+((t-s)>>1);if(l<=s&&t<=r){sum[i]+=z*(t-s+1);sum[i]%=mod;// This is modulolaz[i]+=z;laz[i]%=mod;// This is moduloreturn;}pd(i,s,t);if(mid>=l)add(l,r,s,mid,(i<<1),z);if(mid+1<=r)add(l,r,mid+1,t,(i<<1)|1,z);up(i);}llgetans(intl,intr,ints,intt,inti){// Get the answer, you can refer to the lazy tags above for understandingintmid=s+((t-s)>>1);lltot=0;if(l<=s&&t<=r)returnsum[i];pd(i,s,t);if(mid>=l)tot+=getans(l,r,s,mid,(i<<1));tot%=mod;if(mid+1<=r)tot+=getans(l,r,mid+1,t,(i<<1)|1);returntot%mod;}usingstd::cin;usingstd::cout;intmain(){// Read inputcin.tie(nullptr)->sync_with_stdio(false);inti,j,x,y,bh;llz;cin>>n>>m>>mod;for(i=1;i<=n;i++)cin>>a[i];build(1,n,1);// Build the treefor(i=1;i<=m;i++){cin>>bh;if(bh==1){cin>>x>>y>>z;chen(x,y,1,n,1,z);}elseif(bh==2){cin>>x>>y>>z;add(x,y,1,n,1,z);}elseif(bh==3){cin>>x>>y;cout<<getans(x,y,1,n,1)<<'\n';}}return0;}
Suppose there are \(N\) products placed on a shelf from left to right, numbered from \(1\) to \(N\), where the price of product \(i\) is \(Pi\). Each operation by Little Hi is one of two possibilities. The first is price modification: Little Hi gives an interval \([L, R]\) and a new price \(\textit{NewP}\), and the prices of all products numbered in this interval become \(\textit{NewP}\). The second operation is query: Little Hi gives an interval \([L, R]\), and Little Ho needs to calculate the total price of all products numbered in this interval and tell Little Hi.
#include<iostream>intn,a[100005],d[270000],b[270000];voidbuild(intl,intr,intp){// Build the segment treeif(l==r){d[p]=a[l];return;}intm=l+((r-l)>>1);build(l,m,p<<1),build(m+1,r,(p<<1)|1);d[p]=d[p<<1]+d[(p<<1)|1];}voidupdate(intl,intr,intc,ints,intt,intp){// Update, can refer to the previous two examplesif(l<=s&&t<=r){d[p]=(t-s+1)*c,b[p]=c;return;}intm=s+((t-s)>>1);if(b[p]){d[p<<1]=b[p]*(m-s+1),d[(p<<1)|1]=b[p]*(t-m);b[p<<1]=b[(p<<1)|1]=b[p];b[p]=0;}if(l<=m)update(l,r,c,s,m,p<<1);if(r>m)update(l,r,c,m+1,t,(p<<1)|1);d[p]=d[p<<1]+d[(p<<1)|1];}intgetsum(intl,intr,ints,intt,intp){// Get the answer, same as beforeif(l<=s&&t<=r)returnd[p];intm=s+((t-s)>>1);if(b[p]){d[p<<1]=b[p]*(m-s+1),d[(p<<1)|1]=b[p]*(t-m);b[p<<1]=b[(p<<1)|1]=b[p];b[p]=0;}intsum=0;if(l<=m)sum=getsum(l,r,s,m,p<<1);if(r>m)sum+=getsum(l,r,m+1,t,(p<<1)|1);returnsum;}intmain(){std::ios::sync_with_stdio(false);std::cin>>n;for(inti=1;i<=n;i++)std::cin>>a[i];build(1,n,1);intq,i1,i2,i3,i4;std::cin>>q;while(q--){std::cin>>i1>>i2>>i3;if(i1==0)std::cout<<getsum(i2,i3,1,n,1)<<std::endl;elsestd::cin>>i4,update(i2,i3,i4,1,n,1);}return0;}
Maintain the permanent mark for each interval, and finally run a DFS on the segment tree to collect results. Note to add a pruning optimization when marking, otherwise it will TLE.
Extensions
Segment trees are widely applied, common extensions and variants include:
Application: Segment Tree Optimized Graph Construction
During graph construction and edge addition, we sometimes encounter problems where a point connects to a continuous interval of points, or a continuous interval connects to a point. If we connect them one by one, once the number of points increases, the complexity explodes. Here we can use the interval property of segment trees to optimize our graph construction.
Below is a segment tree.
Each node represents an interval. Suppose we want to connect edges to interval \([2, 4]\).
In some problems, there is also the case where an interval connects to a point. Then we simply reverse all directed edges in the first figure above. The tree above is called the "in-tree", and the one below is called the "out-tree".
#include<bitset>#include<iostream>#include<queue>#include<vector>usingnamespacestd;usingll=longlong;constexprintN=1e5+5;usingpil=pair<int,ll>;usingpli=pair<ll,int>;intn,q,s,tot,rt1,rt2;intpos[N];lldis[N<<3];vector<pil>e[N<<3];bitset<(N<<3)>vis;structseg{intl,r,lson,rson;}t[N<<3];intls(intu){// Left childreturnt[u].lson;}intrs(intu){// Right childreturnt[u].rson;}voidbuild(int&u,intl,intr){// Dynamically build the in-treeu=++tot;t[u]=seg{l,r};if(l==r){pos[l]=u;return;}intmid=(l+r)>>1;build(t[u].lson,l,mid);build(t[u].rson,mid+1,r);e[u].emplace_back(ls(u),0);e[u].emplace_back(rs(u),0);}voidbuild2(int&u,intl,intr){// Dynamically build the out-treeif(l==r){u=pos[l];return;}u=++tot;t[u]=seg{l,r};intmid=(l+r)>>1;build2(t[u].lson,l,mid);build2(t[u].rson,mid+1,r);e[ls(u)].emplace_back(u,0);e[rs(u)].emplace_back(u,0);}voidadd1(intu,intlr,intrr,intv,llw){// Connect point to intervalif(lr<=t[u].l&&t[u].r<=rr){e[v].emplace_back(u,w);return;}intmid=(t[u].l+t[u].r)>>1;if(lr<=mid){add1(ls(u),lr,rr,v,w);}if(rr>mid){add1(rs(u),lr,rr,v,w);}}voidadd2(intu,intlr,intrr,intv,llw){// Connect interval to pointif(lr<=t[u].l&&t[u].r<=rr){e[u].emplace_back(v,w);return;}intmid=(t[u].l+t[u].r)>>1;if(lr<=mid){add2(ls(u),lr,rr,v,w);}if(rr>mid){add2(rs(u),lr,rr,v,w);}}voiddij(intS){priority_queue<pli,vector<pli>,greater<pli>>q;inttot=(n<<2);for(inti=1;i<=tot;++i){dis[i]=1e18;}dis[S]=0;q.emplace(dis[S],S);while(!q.empty()){plifr=q.top();q.pop();intu=fr.second;if(vis[u])continue;for(pilit:e[u]){intv=it.first;llw=it.second;if(dis[v]>dis[u]+w){dis[v]=dis[u]+w;q.emplace(dis[v],v);}}}}intmain(){cin.tie(nullptr)->sync_with_stdio(false);cin>>n>>q>>s;build(rt1,1,n);build2(rt2,1,n);for(inti=1,op,u;i<=q;++i){cin>>op>>u;if(op==1){intv;llw;cin>>v>>w;e[pos[u]].emplace_back(pos[v],w);}elseif(op==2){intl,r;llw;cin>>l>>r>>w;add1(rt1,l,r,pos[u],w);}else{intl,r;llw;cin>>l>>r>>w;add2(rt2,l,r,pos[u],w);}}dij(pos[s]);for(inti=1;i<=n;++i){if(dis[pos[i]]==1e18){cout<<"-1 ";}else{cout<<dis[pos[i]]<<' ';}}return0;}