//generated by GPT-5.6 Terra, modified by hamamu namespace MonotoneHistogramRasterIndexSpace{ using Int = long long; using ll = long long; struct MonotoneHistogramRasterCore{ vector H; //H[i]: 列 i の高さ。広義単調増加列 ll N=0; //列数 /* smH[i]: H[0]+...+H[i-1] を、 「高さ 0 以上になる直前までの総和」を基準にずらしたもの。 これにより、 smH[split] + h*(N-split) が、そのまま行0基準の符号付き面積になる。 */ vector smH; /* 高さが変化しない区間ごとの情報。 行 bottom[t] から行 nextBottom-1 までは、列 [left[t],N-1] にセルが存在。 endArea[t]: その区間の上端の行までにある、行0基準の符号付きセル数 */ vector bottom; vector left; vector width; vector endArea; MonotoneHistogramRasterCore() = default; MonotoneHistogramRasterCore(const vector &H_){ Init(H_); } //---- H を受け取って構築。計算量 O(N) void Init(const vector &H_){ assert(!H_.empty()); H.assign(H_.begin(),H_.end()); N=(ll)H.size(); for(ll i=1;i currentHeight となる最初の列。 すなわち、行 currentHeight における最左列。 */ ll currentHeight=H[0]; ll split=upper_bound(H.begin(),H.end(),currentHeight)-H.begin(); //-- 同じ高さをまとめ、各高さ区間を一つずつ作る while(split= nextHeight) で計算できる。 */ ll areaToNextHeight=smH[split]+nextHeight*(N-split); //-- 高さ区間を保存する bottom.push_back(currentHeight); left.push_back(split); width.push_back(N-split); endArea.push_back(areaToNextHeight); //-- 次の高さ区間へ進む currentHeight=nextHeight; split=next; } } /* 行sjから行ejまでの符号付き面積 O(log N) */ Int area(ll sj,ll ej) const{ if (sj==0) return areaBelow(ej); //高速化 else return areaBelow(ej)-areaBelow(sj-1); } Int leftAt(ll j) const{ //行j最左位置 N:行が空 //行jにセルがある条件は H[i]>j return (Int)(upper_bound(H.begin(),H.end(),j)-H.begin()); } Int widthAt(ll j) const{ return (Int)(N-leftAt(j)); }//行j幅 Int rightAt(ll j) const{ return (Int)(N-1); } //行j最右位置 /* セル(i,j)の行0基準ラスタ順位 1-origin、左下が1 */ Int bottomRank(ll i,ll j) const{ assert(contains(i,j)); //-- 自分より下にあるセル数 + 行内での左からの順位 return areaBelow(j-1)+(Int)(i-leftAt(j)+1); } /* セル(i,j)の行sj基準ラスタ順位 1-origin、左下が1 */ Int bottomRank(ll i,ll j,ll sj) const{ //-- 行sjより下の面積を引く return bottomRank(i,j)-areaBelow(sj-1); } /* セル(i,j)の最上行基準逆ラスタ順位 1-origin、左上が1 */ Int topRank(ll i,ll j) const{ assert(contains(i,j)); //-- 自分より上にあるセル数 + 行内での左からの順位 return area(j+1,H.back()-1)+(Int)(i-leftAt(j)+1); } /* セル(i,j)の行sj基準逆ラスタ順位 1-origin、左上が1 */ Int topRank(ll i,ll j,ll sj) const{ //-- 行sjより上にあるセル数を引く return topRank(i,j)-area(sj+1,H.back()-1); } /* 下からラスタ順位S(1-origin、左下1、S≦0可)のセル(i,j) なし:{-1,-1} */ pair bottomSelect(ll S) const{ /* 行 H[0]-1 以下は幅 N の等幅尾部である。 その最上段までの符号付き面積を求める。 */ ll tailEnd=areaBelow(H[0]-1); if(S<=tailEnd){//等幅尾部に入る場合は、直接戻す ll d=tailEnd-S; ll i=N-1-d%N; ll j=H[0]-1-d/N; return {(Int)i,(Int)j}; } /* それより上は、圧縮した高さ区間に入る。 endArea の最後を超える順位は存在しない。 */ if(endArea.empty() || endArea.back() bottomSelect(ll S,ll sj) const{ //-- 部分領域内の順位を全体順位へ変換する return bottomSelect((ll)areaBelow(sj-1)+S); } /* 上からラスタ順位S(1-origin、左上が1)にあるセル(i,j) */ pair topSelect(ll S) const{ if(S<=0) return {-1,-1}; ll top=H.back()-1; //最上段の行番号 ll upperArea=area(H[0],top);//幅が変化しうる有限部分[H[0],top]の面積 if(S>upperArea){//等幅尾部のとき 各行は幅N、左端0 ll d=S-upperArea-1; ll i=d%N; ll j=(H[0]-1)-d/N; return {(Int)i,(Int)j}; } /* 高さ区間tの「さらに上」のセル数はareaBelow(top)-endArea[t] よって、endArea[t-1] <= areaBelow(top)-S < endArea[t] を満たす区間 t に答がある。 */ ll topArea=areaBelow(top); ll t=upper_bound(endArea.begin(),endArea.end(),topArea-S)-endArea.begin(); ll nextBottom=(t+1<(ll)bottom.size() ? bottom[t+1] : H.back()); ll cellsAbove=topArea-endArea[t];//区間 t より上の面積にあるセル数 ll offset=S-cellsAbove-1;//区間tの上端側から見た 0-origin offset ll i=left[t] +offset%width[t]; ll j=(nextBottom-1)-offset/width[t]; return {(Int)i,(Int)j}; } /* 行sj以下の部分を上からラスタ順に見て、S 番目のセルを返す。O(log N) - S は 1-origin - セルが存在しないときは {-1,-1} */ pair topSelect(ll S,ll sj) const{ ll skipped=area(sj+1,H.back()-1); //行sjより上のセル数 return topSelect(skipped+S); } /* 有効セル (si,sj) からラスタ距離 d だけ進んだ位置を返す。 d は負でもよい。 セルが存在しないときは {-1,-1} */ pair bottomStep(ll si,ll sj,ll d) const{ return bottomSelect((ll)bottomRank(si,sj)+d); } /* 有効セル (si,sj) から逆ラスタ距離 d だけ進んだ位置を返す。 d は負でもよい。 セルが存在しないときは {-1,-1} */ pair topStep(ll si,ll sj,ll d) const{ return topSelect((ll)topRank(si,sj)+d); } /* (si,sj) から (ei,ej) までのラスタ順における符号付き距離。 後なら正、前なら負、同じなら 0。 */ Int bottomDistance(ll si,ll sj,ll ei,ll ej) const{ return bottomRank(ei,ej)-bottomRank(si,sj); } /* (si,sj) から (ei,ej) までの逆ラスタ順における符号付き距離。 後なら正、前なら負、同じなら 0。 */ Int topDistance(ll si,ll sj,ll ei,ll ej) const{ return topRank(ei,ej)-topRank(si,sj); } bool contains(ll i,ll j) const{return 0<=i && i &H_,bool upper_){ Init(H_,upper_); } /* H_ とセルの向きから構築する。計算量 O(N)。 upper==false: セル(i,j)が存在 <=> j < H[i] upper==true: セル(i,j)が存在 <=> H[i] <= j */ void Init(const vector &H_,bool upper_){ assert(!H_.empty()); N=(ll)H_.size(); upper=upper_; bool increasing=true; bool decreasing=true; //-- H が単調増加・単調減少のどちらかを確認する for(ll i=1;iH_[i]) increasing=false; if(H_[i-1] y K(N); for(ll i=0;i bottomSelect(ll S) const{ return bottomSelect(S,bottomStart); } pair bottomSelect(ll S,ll sj) const{ ll sy=toY(sj); //下側(上側)セルの物理ラスタ順は core の bottom(top) 順 auto [x,y]=upper ? core.topSelect(S,sy) : core.bottomSelect(S,sy); if(x<0) return {-1,-1}; //core側で範囲外なら、そのまま返す x=fixSelectX(x,y); //横反転時は、行内位置を反転 return {(Int)fromX(x),(Int)fromY(y)}; //物理座標へ戻す } pair topSelect(ll S) const{ return topSelect(S,topStart); } pair topSelect(ll S,ll sj) const{ ll sy=toY(sj); //下側(上側)セルの物理逆ラスタ順は core の top(bottom) 順 auto [x,y]=upper ? core.bottomSelect(S,sy) : core.topSelect(S,sy); if(x<0) return {-1,-1}; //core側で範囲外なら、そのまま返す x=fixSelectX(x,y); //横反転時は、行内位置を反転 return {(Int)fromX(x),(Int)fromY(y)}; //物理座標へ戻す } pair bottomStep(ll si,ll sj,ll d) const{ return bottomSelect((ll)bottomRank(si,sj)+d); } pair topStep(ll si,ll sj,ll d) const{ return topSelect((ll) topRank(si,sj)+d); } Int bottomDistance(ll si,ll sj,ll ei,ll ej) const{ return bottomRank(ei,ej)-bottomRank(si,sj); } Int topDistance(ll si,ll sj,ll ei,ll ej) const{ return topRank(ei,ej)- topRank(si,sj); } private: ll toX(ll i) const{ //物理座標 i を core 側の x 座標へ変換 return reverseX ? N-1-i : i; } ll fromX(ll x) const{ //core 側の x 座標を物理座標 i へ変換 return reverseX ? N-1-x : x; } ll toY(ll j) const{ //物理座標 j を core 側の y 座標へ変換 return upper ? -j-1 : j; } ll fromY(ll y) const{ //core 側の y 座標を物理座標 j へ変換 return upper ? -y-1 : y; } bool contains(ll i,ll j) const{ return core.contains(toX(i),toY(j)); }//true:セル(i,j)存在 /* 横反転時、core内のx昇順順位を、元座標でのi昇順順位へ直す */ ll fixRankX(ll S,ll x,ll y) const{ if(!reverseX) return S; ll pos=x-core.leftAt(y)+1; //coreでの行内 1-origin 順位 return S+core.widthAt(y)-2*pos+1; //行内順序だけを反転する } /* 横反転時、coreが返したセルを同一行内で反転 */ ll fixSelectX(ll x,ll y) const{ if(!reverseX) return x; ll left=core.leftAt(y), width=core.widthAt(y); return left+width-1-(x-left); //[left,left+width-1]内で左右反転 } }; } // namespace MonotoneHistogramRasterIndexSpace using MonotoneHistogramRasterIndexSpace::MonotoneHistogramRasterIndex; /* 単調増加or減少の高さ列Hが与えられたときの、横走査を扱うクラス H={0,2,2,3,5,6}、セルが下側と上側の様子 j 下側 j 上側 5・・・・・□ 5□□□□□・ 4・・・・□□ 4□□□□・・ 3・・・・□□ 3■■■★・・ 2・・・□□□ 2■■■・・・ 1・■■■★□ 1■・・・・・ 0・■■■■■ 0■・・・・・ . 0 1 2 3 4 5 i . 0 1 2 3 4 5 i .bottomRank(4,1)=9 .bottomRank(3,3)=9 .bottomSelect(9)={4,1} ,bottomSelect(9)={3,3} - Hは空配列不可 - ラスタ順=左下起点、逆ラスタ順=左上起点 とする MonotoneHistogramRasterIndex hc(H,false); //セルがHの下側 MonotoneHistogramRasterIndex hc(H,true); //セルがHの上側 ll i=hc.leftAt (j); //行jの左端位置 ll i=hc.rightAt(j); //行jの右端位置 ll w=hc.widthAt(j); //行jの幅 ll S=hc.area(sj,ej); //行[sj,ej]の面積 ll S=hc.bottomRank(i,j); //行0 から(i,j)までの ラスタ順位1-origin(=面積) ll S=hc.bottomRank(i,j,sj); //行sj から(i,j)までの ラスタ順位1-origin(=面積) ll S=hc.topRank(i,j); //最上行から(i,j)までの逆ラスタ順位1-origin(=面積) ll S=hc.topRank(i,j,sj); //行sj から(i,j)までの逆ラスタ順位1-origin(=面積) 以下全て S:1-origin、S≦0可、セルなし:(-1,-1) auto[i,j]=hc.bottomSelect(S); //行0 から ラスタ順位Sの(i,j) auto[i,j]=hc.bottomSelect(S,sj); //行sjから ラスタ順位Sの(i,j) auto[i,j]=hc.topSelect(S); //最上行から逆ラスタ順位Sの(i,j) auto[i,j]=hc.topSelect(S,sj); //行sj から逆ラスタ順位Sの(i,j) 以下全て d≦0可、セルなし:(-1,-1) auto[i,j]=hc.bottomStep(si,sj,d); //(si,sj)から ラスタ距離dの(i,j) auto[i,j]=hc.topStep(si,sj,d); //(si,sj)から逆ラスタ距離dの(i,j) ll d=hc.bottomDistance(si,sj,ei,ej); //(si,sj)から(ei,ej)の ラスタ距離 ll d=hc.topDistance (si,sj,ei,ej); //(si,sj)から(ei,ej)の逆ラスタ距離 - 負への拡張 - 縦方向のセル存在を、[0,Hi-1]ではなく[-∞,Hi-1]とする - 負のHiも可 */