SRM 735 MaxSquare 「よくある二分探索の変なことをやるやつ」

なんやこのテク...っていう気持ちになったので記事を書きます

http://community.topcoder.com/stat?c=problem_statement&pm=14929

問題の解説...https://www.topcoder.com/blog/single-round-match-735-editorials/

りんごさんの参考解説動画(ICPCの類似した問題)

https://youtu.be/agCN6bPxeE4?t=6052

https://youtu.be/agCN6bPxeE4?t=11979

問題の本質

数列Bが与えられる.
このときf(i,j) = (B[j] - B[i]) * (j - i + 1)の最大値を求めよ.

平面で考えてみる

横軸をindex,縦軸をBの値のと置くと、下のような図ができて

f:id:Kutimoti:20180831214244j:plain

この図でf(1,2)は、下の長方形の面積になります.'

f:id:Kutimoti:20180831214304j:plain

これの最大を求めたい...

最適な右上

ある頂点iを決めたとき、f(i,j)が最大になるjを「最適な右上」と呼ぶことにします.

左下になり得る頂点、右上になり得る頂点

こんな感じになりそう(実際なって証明ができるがなんとなくわかる)

f:id:Kutimoti:20180831214333j:plain

最適な右上が単調増加

もしこの図で

f:id:Kutimoti:20180831214358j:plain

頂点0の最適な右上が3

頂点1の最適な右上が2だとすると

f(0,2) < f(0,3) ==> ABCD < CDE ==> AB < E
f(1,3) < f(1,2) ==> DEFG < BDF ==> EG < B

足すと
ABEG < BE ==> AG < 0(は?)

これは矛盾

つまり

ある左下の頂点Lを決めたときの最適な右上をRとすると、Lより左側の頂点を左下としたときの最適な右上はRより左側、右側についても同様が成り立つ

f:id:Kutimoti:20180831214431j:plain

これを使うと,左下の頂点の区間[left,right),右上の頂点の区間を'[lo,hi)'とすると

(下のsolve関数を見たほうがいいかもしれない)

1.mid = (left + right) / 2番目の左下の頂点をLとする.

2.Lの最適な右上を調べ、indexをrとする.

3.答えを更新

4.[left,mid),[lo,r + 1)に分割して1.をする.

5.[mid + 1,right),[r,hi)に分割して1.をする.

これで答えがO(NlogN)で求まります(すごい)

Source

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
#define rep(i,s,e) for(int (i) = (s);(i) <= (e);(i)++)
#define all(x) x.begin(),x.end()

class MaxSquare{
public:
  using P = pair<i64,i64>;
  vector<P> L,R;

  vector<P> normalize(vector<P> v){
    vector<P> ret;
    for(int i = 0;i < v.size();i++){
      while(ret.size() > 0 && ret[ret.size() - 1].second <= v[i].second){
        ret.pop_back();
      }
      ret.push_back(v[i]);
    }
    return ret;
  }

  vector<P> flip(vector<P> v){
    for(int i = 0;i < v.size();i++){
      v[i].second *= -1;
    }
    reverse(v.begin(),v.end());
    return v;
  }

  i64 solve(int left,int right,int lo,int hi){
    int mid = (left + right) / 2;
    int bestl = -1;
    i64 best = -1;
    for(int i = lo;i < hi;i++){
      i64 cur = (L[mid].first - R[i].first) * (L[mid].second - R[i].second);
      if(cur > best){
        best = cur;
        bestl = i;
      }
    }

    if(left < mid){
      best = max(best,solve(left,mid,lo,bestl + 1));
    }
    if(mid + 1 < right){
      best = max(best,solve(mid + 1,right,bestl,hi));
    }
    return best;
  }

  i64 getMaxSum(i64 n,i64 s,i64 q,i64 o,vector<i64> x,vector<i64> y){

    vector<i64> b(n);
    rep(i,0,n - 1){
      b[i] = (s / (1LL << 20)) % q + o;

      i64 s0 = (s * 621) % (1LL << 51);
      i64 s1 = (s * 825) % (1LL << 51);
      i64 s2 = (s * 494) % (1LL << 51);
      i64 s3 = (s *  23) % (1LL << 51);

      s = s3;
      s = (s * (1LL << 10) + s2) % (1LL << 51);
      s = (s * (1LL << 10) + s1) % (1LL << 51);
      s = (s * (1LL << 10) + s0 + 11) % (1LL << 51);
    }
    for(int i = 0;i < x.size();i++){
      b[x[i]] = y[i];
    }
    auto X = b;
    for(int i = 1;i < n;i++){
      b[i] += b[i - 1];
    }
    vector<P> B;
    B.push_back({-1,0});
    for(int i = 0;i < n;i++){
      B.push_back({i,b[i]});
    }
    L = flip(normalize(flip(B)));
    R = normalize(B);

    i64 ans = 2 * solve(0,L.size(),0,R.size());
    if(ans == 0){
      ans = 2 * X[0];
      for(int i = 0;i < n;i++){
        ans = max(ans , 2 * X[i]);
      }
    }
    return ans;
  }
};