セグメント木を育てる 3

・例題

AOJ_Range Update Query (RUQ)
RMQとの違いは、更新が一点更新ではなく、区間更新となっている点です。
区間なのでセグ木で解けそうですが、区間を更新する際に一つずつ更新するしかなく、O(QN(logN))の計算量がかかります。
N=Q=10^5が最大なので、TLEになります。

・遅延評価セグメント木

上記のような区間更新をO(logN)でできるデータ構造が遅延評価セグメント木です。
https://algo-logic.info/segment-tree/#toc_id_3
ここが分かりやすいいです。

更新の際は必要最低限の区間だけ更新し、必要になった際(次回更新や値の取得時)に必要な個所だけ下に伝播させます。
必要な時に伝播させるため、遅延評価となっています(多分)。

・実装

//0-index
vector<int> segv;
vector<int> sege;
int segv_n;
const int INV_VAL=LLONG_MIN;
void seg_eval(int k){
   if(sege[k]==INV_VAL) return;
   if(k<segv_n){
       sege[k*2+1]=sege[k];
       sege[k*2+2]=sege[k];
   }
   segv[k]=sege[k];
   sege[k]=INV_VAL;
}
void seg_init(int N,int a){
  segv.clear();
  sege.clear();
  int n=1;
  while(n<N) n*=2;
  segv_n=n-1;
  n*=2;
  n=n-1;
  for(int i=0;i<n;i++){
      segv.push_back(a);
      sege.push_back(INV_VAL);
  }
}
void seg_update(int l,int r,int a){
   struct segF{
       static void segupdate(int x,int y,int a,int k,int l,int r){
           seg_eval(k);
           if(r<x||l>y) return;
           if(l>=x&&r<=y) {
               sege[k]=a;
           }
           else{
               segupdate(x,y,a,k*2+1,l,(l+r)/2);
               segupdate(x,y,a,k*2+2,(l+r)/2+1,r);
           }
       }
   };
   segF::segupdate(l,r,a,0,0,segv_n);
}
int seg_find(int x,int y){
  struct segF{
      static int segfind(int x,int y,int k,int l,int r){
          //cout<<x<<","<<y<<","<<k<<","<<l<<","<<r<<endl;
          seg_eval(k);
          if(r<x||l>y) return INV_VAL;
          if(l>=x&&r<=y) return segv[k];
          else{
              int segl=segfind(x,y,k*2+1,l,(l+r)/2);
              int segr=segfind(x,y,k*2+2,(l+r)/2+1,r);
              if(l==x&&r==y) return segv[k];
              if(segl!=INV_VAL) return segl;
              if(segr!=INV_VAL) return segr;
              return INV_VAL;
          }
      }
  };
  return segF::segfind(x,y,0,0,segv_n);
}
void seg_debug(){
  cout<<"---------seg_debug------n="<<segv_n<<endl;
  int k=1;
  for(int i=0;i<(segv_n+1)*2-1;i++){
      if(k==i) {
          cout<<endl;
          k=k*2+1;
      }
      cout<<segv[i]<<","<<sege[i]<<" ";
  }
  cout<<endl<<"------------------------------"<<endl;
}

メインの木(segv)以外に、一旦取っておく用の木(sege)を用意しておきます。

・seg_eval
 segeに保持された値があるなら、その値を下に伝播させる用の関数です。
・seg_init
 segeが増えたため、その分初期化が増えています。
・seg_update
 findと考え方は似ており、最小の区間数でsegeを更新するようにします。
 なお、更新の際に既にsegeに値がある場合は伝播させる必要があります。そうしないと、後から更新した値が、前に更新した値によって上書きされてしまいます。
・seg_find
 ほぼ同じですが、探すときについでに伝播させる必要があります。
・seg_debug
 segeも出力するようにしました。

上記、遅延評価のセグ木を使い、以下のコードを提出しました。

signed main(){
   int n,q;
   cin>>n>>q;
   seg_init(n,pow(2,31)-1);
   for(int i=0;i<q;i++){
       int q;
       cin>>q;
       if(q==0){
           int s,t,x;
           cin>>s>>t>>x;
           seg_update(s,t,x);
       }
       else{
           int x;
           cin>>x;
           cout<<seg_find(x,x)<<endl;
       }
       seg_debug();
   }
}

無事にACです。

・おわりに


ABC177-Fを解くためにセグ木を勉強したので、解いてみたけど無限にバグらせてAC。2日間寝不足になったので諦めました。

あと、Atcoder STL?でセグ木の提供があるとかないとか。
ぬるからは折角なので、自分の育てたセグ木が使いたいですね(愛着もありますし)

この記事が気に入ったらサポートをしてみませんか?