データ構造-ラインセグメントツリー

ラインツリー

1.コンセプト(構築)

ラインセグメントツリーは、二分法のアイデアに基づく完全なバイナリツリーです。これは、データまたは配列の一部を維持し、すべての操作の時間の複雑さをO(nlogn)まで低く保つために使用されます。
データが配置されていないノードの場合、デフォルトは0であり、各ノードに格納される値は、左右の子(リーフノードを除く)によって実行されたいくつかの操作の結果です。
ここに写真の説明を挿入

1.コードの実装

実際には、配列として直接実装できますが、元の配列の左右の間隔の値を格納するために、構造を使用してラインセグメントツリーを実装します

#include<iostream>
using namespace std;

const int N = 1e4 +5;
int arr[N];

struct segmentTree{
    
    
	int l,r;
	int val;
}tree[4*N]; 

void build(int p,int l,int r) {
    
    
	tree[p].l = l;
	tree[p].r = r;
	//终止条件
	if(l==r) {
    
    
		tree[p].val = arr[l];
	} 
	else {
    
    
		//本质上是递归的过程
		int mid = (l+r)/2;
		build(2*p+1,l,mid);
		build(2*p+2,mid+1,r);
		tree[p].val = tree[2*p+1].val + tree[2*p+2].val;
	}
}

int main( ) {
    
    
	for(int i=0;i<=5;i++) {
    
    
		cin>>arr[i];
	}
	build(0,0,5);
	for(int i=0;i<15;i++) {
    
    
		printf("tree[%d,%d]=%d\n",tree[i].l,tree[i].r,tree[i].val);
	}
	return 0;
}

2.実行結果

ツリーの後に間隔値が続き、一部の未入力ノードはすべて0に初期化されます
ここに写真の説明を挿入

2.基本操作

1.クエリ(単一のポイントまたは間隔)

ラインセグメントツリーの間隔とノード間隔の交差のさまざまな状況を照会します。

  1. クエリ間隔には、ラインセグメントツリーノード間隔が完全に含まれています------------ノードtree [p] .sumを直接返します
  2. クエリ間隔とラインセグメントツリーノードの左側の子には交差があります---------再帰的にquery(2 * p + 1、L、R)
  3. クエリ間隔とラインセグメントツリーノードの右の子には交差点があります---------再帰的にquery(2 * p + 2、L、R)

シングルポイントクエリはインターバルクエリの特殊なケースであり、現時点ではL = Rであり、次のコードをわずかに変更するだけです。

int query(int p,int L,int R) {
    
    
	int sum = 0;
	//终止条件
	if(tree[p].l>R||tree[p].r<L)
		sum = 0;//不在范围内直接返回
	else if(tree[p].l>=L&&R>=tree[p].r)
		sum += tree[p].val;//包含其中直接返回这个节点的值
	else {
    
    	
	    //以上两种情况都不包含则是有交集的情况,对左右孩子都递归,不符合会直接返回
		int mid  = (L+R)/2;
		sum += query(2*p+1,L,R);
		sum += query(2*p+2,L,R);
	}
	return sum;
}

テストケース
ここに写真の説明を挿入

2.シングルポイントの変更

シングルポイントを変更することは、シングルポイントクエリを変更することです。変更されたノード値がクエリで見つかった後、各祖先ノードの値が更新されます。

//单点修改 
void update(int p,int ind,int val) {
    
    
	if(ind < tree[p].l||ind > tree[p].r ) 
		return;
	else if(tree[p].r == tree[p].l) {
    
    
		tree[p].val = val;
	}
	else {
    
    
		update(2*p+1,ind,val);
		update(2*p+2,ind,val);
		//这一步很重要,一定不能少
		tree[p].val = tree[2*p+1].val + tree[2*p+2].val;
	}
}

ここに写真の説明を挿入

3.間隔の変更(pushdown()を使用)

間隔の変更とは、特定の間隔の値を変更する必要があることを意味します。特定の間隔のすべての値に1を追加する操作である可能性があります。参加している各祖先ノードと子孫ノードを変更すると、タイムアウトになります。現時点では、行は祖先を変更し、必要に応じて子ノードを変更します。レイジーマーク(ディレイマーク)を使用し、必要なときにマークを使用して操作します。

//区间修改,此处是将[L,R]所有元素全部加一 
void change(int p,int L,int R) {
    
    
	if(R < tree[p].l||L > tree[p].r ) 
		return;
	if(L <= tree[p].l && tree[p].r <= R) {
    
    
		//完全包含其中,直接改变标记并处理然后返回即可 
		tree[p].val += (tree[p].r - tree[p].l +1);
		tree[p].lazy += 1;
		return;
	}
	//处理延迟标记,因为要用到左右儿子节点了 
	pushdown(p);
	int mid = (tree[p].l + tree[p].r)/2;
	if(L <= mid)	change(2*p+1,L,R);
	if(R >  mid) 	change(2*p+2,L,R);
	tree[p].val = tree[2*p+1].val + tree[2*p+2].val;
}

次に、pushdown()関数を紹介します。この関数の関数は、遅延マークを処理することです。マークされたノード、つまりlazyが0でない場合、マークは左右の息子に渡され、次に左右の息子のコンテンツが処理され、最後にこれが処理されます。フラグは処理されたためクリアされます。

void pushdown(int p) {
    
    
	if(tree[p].lazy != 0){
    
    
		tree[2*p+1].lazy += tree[p].lazy;
		tree[2*p+2].lazy += tree[p].lazy;
		int mid = (tree[p].l + tree[p].r)/2;
		tree[2*p+1].val += mid - tree[p].l + 1;
		tree[2*p+2].val += tree[p].r - mid;
		tree[p].lazy = 0;//父节点的标记清零 
	}
}

最後に、間隔の変更ではレイジーマークが使用されるため、この時点でのクエリは上記のクエリではなくなります。pushdown()関数が必要なクエリ関数でもありますが、関数は一般的に変更されていません。

//查询 
int _query(int p,int L,int R) {
    
    
	//终止条件
	if(tree[p].l>R||tree[p].r<L)
		return 0;
		
	int sum = 0;
	pushdown(p);
	if(tree[p].l>=L&&R>=tree[p].r) {
    
    
		sum += tree[p].val;
	} 		
	else {
    
    	
		int mid  = (L+R)/2;
		sum += _query(2*p+1,L,R);
		sum += _query(2*p+2,L,R);
	}
	return sum;
}

3.特別なラインセグメントツリー

さらに2つの特別な乗算ラインセグメントツリーとルート番号ラインセグメントツリーがあり、これらは後で追加されます。

おすすめ

転載: blog.csdn.net/weixin_45203704/article/details/107256918