ラインツリー
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.クエリ(単一のポイントまたは間隔)
ラインセグメントツリーの間隔とノード間隔の交差のさまざまな状況を照会します。
- クエリ間隔には、ラインセグメントツリーノード間隔が完全に含まれています------------ノードtree [p] .sumを直接返します
- クエリ間隔とラインセグメントツリーノードの左側の子には交差があります---------再帰的にquery(2 * p + 1、L、R)
- クエリ間隔とラインセグメントツリーノードの右の子には交差点があります---------再帰的に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つの特別な乗算ラインセグメントツリーとルート番号ラインセグメントツリーがあり、これらは後で追加されます。。。