公式

C - Sneaking Glances 解説 by physics0523


座標が正なら負の方向に、負なら正の方向に移動するという貪欲法は失敗します。
例えば \(L=(40,50,90,1,1,1)\) の時、貪欲法に従うと座標 \(0\)\(3\) 回通過することになりますが、最初に負・負・正の方向を選択することで座標 \(0\)\(5\) 回通過することができます。


\(N\) が小さいことを利用しましょう。
「正か負か」という \(2\) 択を \(N\) 回行う、全部で \(2^N\) 通りの選択を全探索する場合、 bit全探索 (解説記事) が便利です。

  • \(i=0,1,\dots,2^N-1\) について、以下を繰り返す。
    • \(j=0,1,\dots,N\) について、 \(i\)\(2\) 進表記した時に \(2^j\) の位が \(0\) なら負の方向に、 \(1\) なら正の方向に移動することにする。

シミュレートの際、 \(0.5\) という数を扱うのは不便なので、座標・移動距離ともに \(2\) 倍して考えると全てを整数で取り扱うことができて実装が楽です。
つまり、最初に座標 \(1\) 、全ての移動を距離 \(2L_i\) だけ行うことにすればよいです。
また、 \(0\) を跨いだがどうか判定するには、移動前の座標を \(x\) 、移動後の座標を \(y\) として \(xy<0\) かどうかで判定できます。そのまま判定するとオーバーフローのおそれがあるため、符号を保ったまま値を \(-1,0,1\) のいずれかに圧縮してから判定するとよいです。

時間計算量は \(O(2^N N)\) です。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;
using ll=long long;

ll sgn(ll x){
  if(x>0){return 1;}
  else if(x<0){return -1;}
  return 0;
}

int main(){
  ll n;
  cin >> n;
  vector<ll> l(n);
  for(auto &nx : l){
    cin >> nx;
    nx*=2;
  }
  ll best=0;
  for(ll i=0;i<(1ll<<n);i++){
    ll pos=1,cur=0;
    for(ll j=0;j<n;j++){
      ll npos=pos;
      if(i&(1ll<<j)){npos+=l[j];}
      else{npos-=l[j];}
      if(sgn(pos)*sgn(npos)<0){cur++;}
      pos=npos;
    }
    best=max(best,cur);
  }
  cout << best << "\n";
  return 0;
}

投稿日時:
最終更新: