Official

E - フ/7 Editorial by penguinman


まず前提として、あるフの字の削除の仕方に対し、それにおいて原点から全体が見えないようなフの字をすべて削除しても原点から全体が見えるものの個数は変わりません。

よって、この問題は以下のように言い換えられます。

\(N\) 個のフの字からいくつかを削除し、残ったフの字がすべて原点から見えるようにしたい。このとき、残るフの字の個数の最大値を求めよ。

さらに、各フの字を両端の \(2\) 点の偏角に対応した開区間と見做すことで、次のように言い換えることも可能です。

\(N\) 個の開区間があり、\(i\) 個目の区間の左端は \(f(x_i,y_i-1)\)、右端は \(f(x_i-1,y_i)\) である。ここで、\(2\) 実数 \((i,j)\) に対し \(f(i,j)\) は座標 \((i,j)\) の偏角を表す。

これらの区間からどの \(2\) 組も共通部分を持たないようにいくつかを選ぶとき、選ぶ区間の個数の最大値を求めよ。

これは、区間スケジューリング問題と呼ばれる有名問題に等しいです。区間スケジューリング問題を以下のような貪欲法によって解くことができるのは非常に有名であり、この問題についても同様に解くことが可能です。

以下の操作を無限に繰り返す。

  1. すでに選んだ区間たちのうち、最も右端の座標が大きいものの(右端の)座標を \(R_{\max}\) とする。まだ選んでいない座標のうちその左端の座標が \(R_{\max}\) 以上であるものが存在するなら、そのようなもののうち区間の右端の座標が最も小さいものを新たに選ぶ。
  2. そうでなく、まだ選んでいない区間の中にその左端の座標が \(R_{\max}\) 以上であるものが存在しないならばこれ以降の操作を打ち切る。

実装の際は事前に \(N\) 個の区間を右端の座標の昇順に並び替えておくとよいです。計算量は \(O(N \log N)\) となります。

実装例 (C++)

#include<bits/stdc++.h>
using namespace std;

using ll = long long;

// p/q
struct fraction{
    ll p,q;
    fraction(ll P = 0, ll Q = 1): p(P), q(Q){}
    bool operator<(const fraction &other)const{
        return p*other.q < other.p*q;
    }
    bool operator<=(const fraction &other)const{
        return p*other.q <= other.p*q;
    }
};

int main(){
    int N; cin >> N;
    vector<ll> x(N),y(N);
    for(int i=0; i<N; i++) cin >> x[i] >> y[i];
    vector<pair<fraction,fraction>> data(N);
    for(int i=0; i<N; i++) data[i] = make_pair(fraction(y[i],x[i]-1),fraction(y[i]-1,x[i]));
    sort(data.begin(),data.end());
    int ans = 0;
    fraction now;
    for(auto p: data){
        if(now <= p.second){
            ans++;
            now = p.first;
        }
    }
    cout << ans << endl;
}

posted:
last update: