かっつのメモ帳

主に競プロ 時々日記

std::setで区間を管理する奴

追記:これ分かりにくいので他ブログ見た方がいいです。僕も使いにくくて困ってるのでまた整備しようと思います。

区間をsetでやる奴苦手意識あるのでメモを残せるように書いた。

現状大したことは書いていないです。

やりたいこと

互いに重なりうる半開区間 [x,y) が何個か与えられて、それらをマージして互いに重なり合わない区間の集合として表したい。

[3,5) , [5,7) , [8,9)[3,7) , [8,9)

上記の例を [3,5) , [5,7) , [8,9) と扱いたい場面はありそう、でもこれは実装を少し変えるだけで(等号の有無で)実現できます。

これはstd::setで簡単に実装ができます。イテレータ周りはバグらせやすいので適宜番兵を入れるなどの注意が必要です。

int main(){
    int n; cin >> n; // n個の区間 [x,y] が与えられると想定
    set<pair<ll,ll>> st;
    // 番兵として、[-INF,-INF) [INF,INF) をsetの中に入れておく
    st.insert(make_pair(-2e18,-2e18));
    st.insert(make_pair(2e18,2e18));
    for(int i=0;i<n;i++){
        ll x,y; cin >> x >> y; y++; // 開区間として右端を扱う
        auto it=st.lower_bound(make_pair(x,y)); it--;
        // 今見ている区間の左端が他の区間と被っていたらマージする
        // 右側の条件の等号は場面によって変わることがある
        // [3,4),[4,5) を [3,5)と扱うかどうか(扱うなら等号がつく)
        if(it->first <= x and x <= it->second){
            x=min(x,it->first); y=max(y,it->second);
            st.erase(it);
        }
        it=st.lower_bound(make_pair(x,y));
        while(1){ // 再帰的に区間を処理
            if(x <= it->first and it->first <= y){ 
                y=max(y,it->second);
                it=st.erase(it); // 削除された要素の次を指すイテレータを返す
            }
            else break;
        }
        st.insert(make_pair(x,y));
    }
}

Yukicoder No.674 n連勤

まさにこれが使える問題です。 実装

またバグらせたら更新するかもしれません。

練習問題

追加しました(2020/12/09)