beet's soil

競プロのことなど

セグ木の実装例

ソースを載せると文字数こわれるので分けることにしました。

↓これの実装例のつもりです。
セグメント木について - beet's soil

githubにも上げておきます。
library/segtree at master · beet-aizu/library · GitHub

あとこれは宣伝なんですが僕の競プロ用のライブラリのリンクも貼っておきます。
GitHub - beet-aizu/library: ライブラリ群


共通

T, E はそれぞれは要素、作用素の型です。
f, g, h はそれぞれ要素のマージ、作用素を作用させる、作用素のマージ用のラムダ式です。
d1, d0 はそれぞれ要素、作用素単位元です。
v は要素の初期値を渡します。渡さない場合は単位元で埋められます。
p は区間に対して作用素を作用させる回数が長さに応じて変化する場合用のラムダ式です。

dat は要素を保持しておくための配列です。
laz は作用素を保持しておくための配列です。

init() では要素数に応じて動的に配列を確保します。
build() では初期値が与えられた場合に O(N)で構築をします。

言葉ではわかりにくいと思うので、一通り例を示します。

うし木

一点更新区間取得の一番簡単なタイプです。
長いので僕はうし木と呼びます(こ"うし"んなので(ア

#include<bits/stdc++.h>
using namespace std;
template <typename T,typename E>
struct SegmentTree{
  typedef function<T(T,T)> F;
  typedef function<T(T,E)> G;
  int n;
  F f;
  G g;
  T d1;
  vector<T> dat;
  SegmentTree(int n_,F f,G g,T d1,
	      vector<T> v=vector<T>()){
    this->f=f;
    this->g=g;
    this->d1=d1;
    init(n_);
    if(n_==(int)v.size()) build(n_,v);
  }
  void init(int n_){
    n=1;
    while(n<n_) n*=2;
    dat.clear();
    dat.resize(2*n-1,d1);
  }
  void build(int n_, vector<T> v){
    for(int i=0;i<n_;i++) dat[i+n-1]=v[i];
    for(int i=n-2;i>=0;i--)
      dat[i]=f(dat[i*2+1],dat[i*2+2]);
  }
  void update(int k,E a){
    k+=n-1;
    dat[k]=g(dat[k],a);
    while(k>0){
      k=(k-1)/2;
      dat[k]=f(dat[k*2+1],dat[k*2+2]);
    }
  }
  T query(int a,int b,int k,int l,int r){
    if(r<=a||b<=l) return d1;
    if(a<=l&&r<=b) return dat[k];
    T vl=query(a,b,k*2+1,l,(l+r)/2);
    T vr=query(a,b,k*2+2,(l+r)/2,r);
    return f(vl,vr);
  }
  T query(int a,int b){
    return query(a,b,0,0,n);
  }
};

作用素を保存する必要がないので h, laz はないです。
update() では更新をしたあと親をたどって全体に反映させます。
query() では区間を高々 O(\log N)個に分割したあとマージしています。

実装例

一番簡単なRMQを実装してみます。
問題: https://onlinejudge.u-aizu.ac.jp/#/courses/library/3/DSL/2/DSL_2_A
解答例: https://onlinejudge.u-aizu.ac.jp/#/solutions/problem/DSL_2_A/review/2566519/beet/C++11

signed main(){
  int n,q;
  cin>>n>>q;
  SegmentTree<int,int> rmq(n,
			   [](int a,int b){return min(a,b);},
			   [](int a,int b){return b;},
			   INT_MAX);
  for(int i=0;i<q;i++){
    int c,x,y;
    cin>>c>>x>>y;
    if(c) cout<<rmq.query(x,y+1)<<endl;
    else rmq.update(x,y);
  }
  return 0;
}

区間に対して最小値を求めたいので f には min 関数を渡しています。
更新は更新後の値に置き換える作用と表現できます。
(int, min) は INT_MAX を単位元とするモノイドなので d1 = INT_MAX となります。

らて木

区間更新一点取得(更新が可換)のあまり見ないタイプです。
名前の由来は適当です。(ukuku09つながり?

#include<bits/stdc++.h>
using namespace std;
template <typename T,typename E>
struct SegmentTree{
  typedef function<T(T,E)> G;
  typedef function<T(E,E)> H;
  int n;
  G g;
  H h;
  T d1;
  E d0;
  vector<T> dat;
  vector<E> laz;
  SegmentTree(int n_,G g,H h,T d1,E d0,
	      vector<T> v=vector<T>()){
    this->g=g;
    this->h=h;
    this->d1=d1;
    this->d0=d0;
    init(n_);
    if(n_==(int)v.size()) build(n_,v);
  }
  void init(int n_){
    n=1;
    while(n<n_) n*=2;
    dat.clear();
    dat.resize(n,d1);
    laz.clear();
    laz.resize(2*n-1,d0);
  }
  void build(int n_, vector<T> v){
    for(int i=0;i<n_;i++) dat[i]=v[i];
  }
  void update(int a,int b,E x,int k,int l,int r){
    if(r<=a||b<=l) return;
    if(a<=l&&r<=b){
      laz[k]=h(laz[k],x);
      return;
    }
    update(a,b,x,k*2+1,l,(l+r)/2);
    update(a,b,x,k*2+2,(l+r)/2,r);
  }
  void update(int a,int b,E x){
    update(a,b,x,0,0,n);
  }
  T query(int k){
    T c=dat[k];
    k+=n-1;
    E x=laz[k];
    while(k>0){
      k=(k-1)/2;
      x=h(x,laz[k]);
    }
    return g(c,x);
  }
};

区間取得がないので f はないです。
update() では影響する区間作用素をマージしています。
query() では影響のある作用素をマージしたあと、要素に作用させています。

実装例

実はらて木をこの問題以外で使ったことないです(ア
問題: https://onlinejudge.u-aizu.ac.jp/#/courses/library/3/DSL/2/DSL_2_E
解答例: https://onlinejudge.u-aizu.ac.jp/#/solutions/problem/DSL_2_E/review/2566524/beet/C++11

signed main(){
  int n,q;
  cin>>n>>q;
  SegmentTree<int,int> raq(n,
			   [](int a,int b){return a+b;},
			   [](int a,int b){return a+b;},
			   0,0);
  for(int i=0;i<q;i++){
    int c,s,t,x;
    cin>>c;
    if(c){
      cin>>x;
      cout<<raq.query(x-1)<<endl;
    }else{
      cin>>s>>t>>x;
      raq.update(s-1,t,x);
    }
  }
  return 0;
}

操作が両方とも整数の加算なので冗長になっています。
足すだけです。


疲れたのでまた今度にします。
TODO: びーと木、Starry Sky木、遅延セグ木