Official

G - Laser Marking Editorial by physics0523


\(N \le 6\) という非常に小さな制約に着目しましょう。
この制約であれば、ありうるレーザ照射の方法を全探索できそうです。

全探索をするのであれば、それに伴って調べ尽くす必要のあるパラメータを全て見つけて固定してしまいましょう。

  • 線分を描く順序 \(N!\) 通り
  • 線分を描く向き(どちらの点からどちらの点へと描くか) \(2^N\) 通り

\(N = 6\) に対して、全体で \(6! \times 2^6 = 46080\) 通りしかなく、これはコンピュータを用いれば容易に全探索できる数です。

線分を描く順序は、長さ \(N\) の順列を全列挙することで尽くせます。
再帰関数を用いて順列を全列挙したり、 C++ においては next_permutation を利用すると便利です。

線分を描く向きは、 \(0\) から \(2^N-1\) までの整数について下から \(i\) bit目が \(0\) の時は \((A_i,B_i) \rightarrow (C_i,D_i)\)\(1\) の時はこの逆と取り決めることで尽くせます。

線分を描く順序と、線分を描く向きとを固定すれば、それを最短の時間で実現する方法は定まります(始点または線分の終わりから次の線分の始まりまでを一直線に移動するのが最善です)。

よって、この2つを二重ループの構造でループさせると、 \(N! \times 2^N\) 通りの全探索が実現します。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;
using pi=pair<int,int>;

long double dist(pi l,pi r){
  long long v=0;
  v+=(l.first-r.first)*(l.first-r.first);
  v+=(l.second-r.second)*(l.second-r.second);
  return sqrtl(((long double)v));
}

int main(){
  int n,s,t;
  cin >> n >> s >> t;
  vector<pi> x(n),y(n);
  for(int i=0;i<n;i++){
    cin >> x[i].first >> x[i].second >> y[i].first >> y[i].second;
  }

  long double res=8e18;
  vector<int> p;
  for(int i=0;i<n;i++){
    p.push_back(i);
  }

  do{
    for(int i=0;i<(1<<n);i++){
      long double cres=0.0;
      pi cur=make_pair(0,0);
      for(int j=0;j<n;j++){
        int el=p[j];
        if(i&(1<<j)){
          cres+=( dist(cur,x[el])/((long double)s) );
          cres+=( dist(x[el],y[el])/((long double)t) );
          cur=y[el];
        }
        else{
          cres+=( dist(cur,y[el])/((long double)s) );
          cres+=( dist(y[el],x[el])/((long double)t) );
          cur=x[el];
        }
      }
      res=min(res,cres);
    }
  }while(next_permutation(p.begin(),p.end()));

  cout << fixed << setprecision(12) << res << "\n";
  return 0;
}

posted:
last update: