よい問題設定。
https://code.google.com/codejam/contest/8224486/dashboard#s=p2
問題
1周360度分の円形のコースがある。
あなたは始点から初めてコースを1周周り始点に戻るように走りたい。
その際、走る速度は任意のタイミングで任意に調節できる。
このコードには、他に多数の集団が走っている。
ある集団は、あなたの出発時に始点からD[i]の位置におり、H[i]人で構成されていて、各人は角度1度あたりM[i]~M[i]+H[i]-1だけ時間がかかる(バラバラな)速度で走っている。
あなたは1周する間に、人と出会う買う数を出来るだけ少なくなるように走りたい。
最小で何人と出会う走り方ができるか。
解法
この問題は(恐らく入力の都合で)他人のパラメータが集合で与えられる。
ただ、本質的に集合であることに意味はないので、集合をばらしてN人(N=sum(H[i]))が初期D[i]度の位置にいて1度あたりM[i]の速度で走っている、と考える。
各人が最初に始点を通過する時刻をT[i]とする。
あなたがT[i]-eps時間でゴールするように走ると、以下の人とすれ違う事になる。
- T[i]より遅い時間にゴールする人を1回ずつ追い抜く。
- T[i]より速くゴールする人について、T[i]時点で始点をR[i]回通過してるとすると、(R[i]-1)回ずつ追い抜かれる。
よって上記2値の和が最小となるようなiを求めればよい。
前者は容易に計算できる。後者は各人をT[i]の小さい順にソートし、以下の手順で計算できる。
T[i]の人がゴールするまでには、T[i-1]の人がゴールするまでに抜かれた人にも当然抜かれる。
あとはT[i-1]~T[i]の時間にさらに何人に抜かれるかを加算する。
i番の人を処理する場合、0~(i-1)番の人が次に始点に到着する時刻をpriority_queueで管理する。
その時刻がT[i]より早い人がいる場合、その都度追い越された回数を加算して、次にもう1周するタイミングをpriority_queueに追加していく。
すごく遅い人がいると、その間に早い人に何周も追い越され、priority_queueの更新処理回数が非常に大きくなる可能性がある。
ただし、この問題では時刻T[0]-epsに到着するように走ることで解が(N-1)以下となることが確定するので、追い越された回数が(N-1)回を超えたら以降は処理を打ち切ってよい。
int N; ll D[1010],H[1010],M[1010]; pair<ll,ll> P[505000]; int slower[505000]; void solve(int _loop) { int f,i,j,k,l,x,y; cin>>N; x=0; FOR(i,N) { cin>>D[i]>>H[i]>>M[i]; FOR(j,H[i]) { P[x].second=M[i]+j; P[x].first=(360-D[i])*(M[i]+j); x++; } } N=x; sort(P,P+N); for(i=N-1;i>=0;i--) { if(P[i].first==P[i+1].first) slower[i]=slower[i+1]; else slower[i]=N-1-i; } ll mi=N; ll faster=0; priority_queue<pair<ll,ll> > Q; FOR(i,N) { while(Q.size()) { auto r=Q.top(); if(-r.first>P[i].first) break; if(++faster>N) break; Q.pop(); Q.push(make_pair(r.first-360LL*r.second,r.second)); } Q.push(make_pair(-P[i].first-360LL*P[i].second,P[i].second)); mi=min(mi,faster + slower[i]); if(faster>N) break; } _P("Case #%d: %lld\n",_loop,mi); }
まとめ
ちょっとややこしいけど、面白かったです。