公式

G - Points and Comparison 解説 by physics0523


\(x\) 軸を右方向、 \(y\) 軸を上方向にとるものとします。(通常の \(xy\) 平面と同様です)
このとき、問題は以下のように言い換えられます。

  • \(xy\) 平面上の \(N\) 点が与えられる。以下の \(Q\) 個の質問に対する答えを足し合わせよ。
    • \(xy\) 平面上の直線が与えられるので、その直線上またはその直線よりも上側に存在する点の数を求めよ。

各質問に対し、全ての点について \(Y \ge A \times X + B\) かどうかを判定するので、その質問の時点で点が \(- A \times X + Y\) に関してソートできていればよいです(これをソート S とします)。なので、これを実現することを考えます。

まず、質問の直線を \(A\) の昇順にソートすることを考えます。

\(A = -\infty\) である場合から始めることになりますが、この場合は単に \((X,Y)\) を辞書順にソートすればよいです。

ソート S 上でのある \(2\)\((X_l,Y_l)\)\((X_r,Y_r)\) の順序を考えましょう。

  • \(X_l = X_r\) である場合は常に \(Y_l < Y_r\) が保たれるように並びます。 (この \(2\) 点の順序が入れ替わることはありません。)
  • そうでない場合、これらを結ぶ直線の傾きを超えるタイミングでソート S 上での順序が反転します。

以上から、例えば以下の解法が成立します。

  • ソート S が成立するように点列を保持することを考える。
  • 点列の隣接する \(2\) 点に関して、それらが今後傾きを増やしていくにあたって入れ替わるなら、入れ替わる傾きを記録しておき priority_queue で管理する。
  • 実際に質問を処理していく。質問の処理の前に priority_queue を参照し、入れ替わっているべき \(2\) 点があるならそれがなくなるまで隣接点の swap を行う。
  • 各質問は二分探索で処理する。

以上より、この問題に時間計算量 \(O(Q (\log Q + \log N) + N^2 \log N)\) で正解できます。

値を代入すると \(6.6 \times 10^8\) となりますが、実行時間制限が 10s であるため定数倍の悪い実装をしなければこの解法で AC を取ることができます。

実装例(C++):

#include<bits/stdc++.h>

using namespace std;
using pl=pair<long long,long long>;
using ppl=pair<pl,long long>;

long long g;
long long md=((1ll<<31)-1);
long long gen(){
  g = (48271*g) % md;
  return g;
}

long long comp_pl(pl a,pl b){
  long long l=a.first*b.second;
  long long r=a.second*b.first;
  if(l<r){return -1;}
  if(l>r){return 1;}
  return 0;
}

int main(){
  long long n;
  cin >> n;
  vector<pl> p(n);
  for(long long i=0;i<n;i++){
    cin >> p[i].first >> p[i].second;
  }
  sort(p.begin(),p.end());

  priority_queue<ppl,vector<ppl>,decltype(
    [](ppl a,ppl b){
      long long cp=comp_pl(a.first,b.first);
      if(cp==0){return (a.second>b.second);}
      if(cp==-1){return false;}
      return true;
    }
  )> pq;

  for(long long i=1;i<n;i++){
    if(p[i-1].first<p[i].first){
      ppl x;
      x.first={p[i].second-p[i-1].second,p[i].first-p[i-1].first};
      x.second=i;
      pq.push(x);
    }
  }

  long long q,ra,rb;
  cin >> q >> g >> ra >> rb;

  vector<pl> ask;
  for(long long i=0;i<q;i++){
    long long g1=gen();
    long long g2=gen();
    long long g3=gen();

    long long a = -ra + (g1%(2*ra+1));
    long long b = -rb + ((g2*md+g3)%(2*rb+1));
    ask.push_back({a,b});
  }

  sort(ask.begin(),ask.end());

  long long res=0;
  for(long long ak=0;ak<q;ak++){
    long long a=ask[ak].first;
    long long b=ask[ak].second;
    while(!pq.empty()){
      ppl od=pq.top();
      long long i=od.second;
      if(
        od.first!=make_pair(p[i].second-p[i-1].second,p[i].first-p[i-1].first)
      ){pq.pop();continue;}

      pl cp=od.first;
      pl cd={a,1};
      if(comp_pl(cp,cd)!=-1){break;}

      pq.pop();
      swap(p[i-1],p[i]);
      i--;
      if(i>0 && p[i-1].first<p[i].first){
        ppl x;
        x.first={p[i].second-p[i-1].second,p[i].first-p[i-1].first};
        x.second=i;
        pq.push(x);
      }
      i+=2;
      if(i<n && p[i-1].first<p[i].first){
        ppl x;
        x.first={p[i].second-p[i-1].second,p[i].first-p[i-1].first};
        x.second=i;
        pq.push(x);
      }
    }

    long long l=0,r=n-1;
    while(l<=r){
      long long te=(l+r)/2;
      if(p[te].second >= a*p[te].first+b){r=te-1;}
      else{l=te+1;}
    }

    res+=(n-1-r);
  }

  cout << res << "\n";
  return 0;
}

投稿日時:
最終更新: