procon2019
このアカウントは京大マイコンクラブ(KMC)の2019年度の競技プログラミング練習会Normal(初心者向け)での説明用に作成したスライドです。 閲覧・参照はご自由にどうぞ。
第14回
セグメント木、(BIT、)平方分割
担当 : laft
KMC-ID:laft
制約
マグマから群に進むに連れて、条件が厳しくなる。
これはRMQの例。
一番下の行に元の配列を入れておく。
その1つ上の行には、各ノードの2つの子のうち小さい方を入れる。
その1つ上の行には、各ノードの2つの子のうち小さい方を入れる。
その1つ上の行には、各ノードの2つの子のうち小さい方を入れる。
その1つ上の行には、各ノードの2つの子のうち小さい方を入れる。
この結果、例えば緑の値は赤の値の区間minとなる。
この結果、例えば緑の値は赤の値の区間minとなる。
この結果、例えば緑の値は赤の値の区間minとなる。
この性質によってクエリを高速に処理できるようになる。
ex. a5からa13の最小値を求める。
ex. a5からa13の最小値を求める。(0-indexed)
この赤い部分が指定した5~13番目の範囲。
ex. a5からa13の最小値を求める。(0-indexed)
セグ木の性質から、この部分だけで十分。
ex. a5からa13の最小値を求める。(0-indexed)
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
赤線の外なので探索打ち切り!
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
赤線の外なので探索打ち切り!
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
赤線の内なので必要な解!
探索は打ち切る。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
赤線の内なので必要な解!
探索は打ち切る。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
上から欲しいものを得るまで探索する。
ex. a5からa13の最小値を求める。(0-indexed)
緑のminをとれば十分!!
ex. a3を4に書き換える。(0-indexed)
下から順に書き換えていく。
ex. a3を4に書き換える。(0-indexed)
下から順に書き換えていく。
ex. a3を4に書き換える。(0-indexed)
下から順に書き換えていく。
ex. a3を4に書き換える。(0-indexed)
下から順に書き換えていく。
ex. a3を4に書き換える。(0-indexed)
下から順に書き換えていく。
ex. a3を4に書き換える。(0-indexed)
下から順に書き換えていく。
完全二分木を作るために、最初にN以上の2べき(2x)のサイズだったことにする。
完全二分木は配列で管理できる!
そこで、下のようにindexを振ることにする。
親から子へのアクセス。
親のindexをnとすると、2*n+1, 2*n+2でそれぞれの子へアクセスできる。
逆に、子から親へのアクセス。
子のindexをnとすると、
⌊2n−1⌋でアクセスできる。
struct rmq{ int n; //size vector<int> segtree; //完全二分木を格納した配列 const int INF = INT_MAX; rmq(int sz){ n = 1; while(n<sz) n<<=1; //配列サイズをsz以上の2冪に。 //セグ木のサイズは2*n-1, 取り敢えず全て単位元で初期化しておく。 segtree.assign(2*n-1,INF); } int find(int l, int r){ //後で } void update(int i, int x){ //後で } };
struct rmq{ int n; //size vector<int> segtree; //完全二分木を格納した配列 const int INF = INT_MAX; rmq(int sz){} //やった。 // [l,r)が欲しいminの区間。 // [a,b)は現在見ている頂点の持つminの区間。最初は全区間。 // kはその頂点が格納されているindex // b=nとしてデフォルト引数にメンバ変数nを使いたかったが、 // できないのでオーバーロード int find(int l, int r){ return find(l,r,0,n,0); } int find(int l, int r, int a, int b, int k){ if(b<=l || r<=a) return INF; //区間外 else if(l<=a && b<=r) return segtree[k]; //区間内 //それ以外なら、子を調べてその結果をminしたものを返す。 else return min(find(l,r,a,(a+b)/2,2*k+1), find(l,r,(a+b)/2,b,2*k+2)); } void update(int i, int x){} //後で };
struct rmq{ int n; //size vector<int> segtree; //完全二分木を格納した配列 const int INF = INT_MAX; rmq(int sz){} //やった。 int find(int l, int r){} //やった void update(int i, int x){ segtree[i+=n-1] = x; //元の配列のindexは頂点数2*n-1より、n-1足したもの。 while(i>0){ i = (i-1)/2; //親に移動 //子同士を比較して小さい方を親の値として更新。 segtree[i] = min(segtree[2*i+1],segtree[2*i+2]); } } };
struct rmq{ int n; //size vector<int> segtree; //完全二分木を格納した配列 const int INF = INT_MAX; rmq(int sz){} //やった。 int find(int l, int r){} //やった void update(int i, int x){} //やった }; int main(){ rmq tr(10); tr.update(5,3); //5番目の要素を3に更新する。 cout << tr.find(0,3) << endl; //INFが出力される cout << tr.find(4,6) << endl; //3が出力される }
群には逆元があるので、計算で求められる場所を省略できる。
群には逆元があるので、計算で求められる場所を省略できる。
群には逆元があるので、計算で求められる場所を省略できる。
By procon2019
発表日時 2019年10月4日(金) 18:30-21:00