yukicoder No.635 自然門松列
「3月中に毎日1問解いて黄色になろう!」キャンペーン4日目のつもり。Twitterで普通に呟いていましたが、射撃部の大会があったので普通に競プロはお休みしてCodinGameやってました(すっとぼけ)
今回の問題はガッチガチの高校数学で攻めました。
問題概要
門松列200本ノック。3本の木がt秒後にはxt+yの長さになっているので、門松列になる瞬間が存在するか判定せよ。
解説
さて、取り敢えず分かりやすいように表記を変えて、x秒後の長さを
1本目:ax+b
2本目:cx+d
3本目:ex+f
とします。S:(1本目-2本目)とT:(3本目-2本目)を算出すると、
S:(a-c)x+(b-d)
T:(e-c)x+(f-d)
このSとTが両方正または負になるx≧0が存在すればYESを出力します。
ところで、SとTが同じ符号→S*T>0ですね。S,T=0は同じ長さなので処理しません。f=S*Tとすると、
f={(a-c)x+(b-d)}*{(e-c)x+(f-d)}=Ax2+Bx+C
です。後半面倒なので簡単な文字に置き換えましたが、2次式、つまり放物線です。
再度確認すると、f>0となるx≧0が存在すればYES、存在しないならNOです。Aの値で場合分けします。
- A>0→xをとても大きくすればfは正になるので無条件にYES。
- A=0→直線です。B>0なら右上がりの直線なのでYES、B≦ならC>0ならYESです。
- A<0→上に凸の放物線で、Ax2+Bx+C=0が2つの異なる実数解を持っていないとf>0になり得ません。つまりB2-4AC≦0ならNOです。今等号も含めましたが、等号成立時は重解で、f=0にはなりますが、f>0にはなりません。2つの異なる実数解をp<qとすると、x∈[p,q]でf>0です。p<qなのでq>0ならよさそうです。実際には、2次方程式の解の公式の分子-B±√Dのうち大きな方がどちらか分からないので、両方試しました。
ここで、少し前にS,T=0で同じ長さの処理が出来る的な文章を書きましたが、欠陥があります。「全ての松の長さが異なる」が条件ですが、ここでS*T=0となるのは1本目=2本目と2本目=3本目の瞬間だけです。つまり、1本目=3本目だと死にます。しかし、今までの処理でYESとなった場合、その解からほんの少しだけずらしたときにaとeが異なればf>0としたまま1本目≠3本目とできます。逆に、a=eだと出来なくて、その時は1本目と3本目のパラメータは完璧に同じです。つまり、[a,b]==[e,f]です。簡単に弾けますね?
ここまでの内容を実装すると解けます。1クエリの計算量は長いですがO(1)です。今回はPythonの気分でした(最初小数点誤差が怖くてDecimal使ったらTLEを返されました)
https://yukicoder.me/submissions/242973
def solve(a,b,c,d,e,f): if [a,b]==[c,d] or [c,d]==[e,f] or [e,f]==[a,b]: return False # 元のやつはax+b,cx+d,ex+fで成長。差を取って、 # s:(a-c)x+(b-d) # t:(e-c)x+(f-d) sa=a-c sb=b-d ta=e-c tb=f-d # sとtが同符号、つまりs*t>0となるxがx>=0に存在すればOK。 # (sa*x+sb)*(ta*x+tb)=Ax^2+Bx+C>0だよね。 A=sa*ta B=sa*tb+sb*ta C=sb*tb if A>0: # x=inf でs*t>0。だからOK。 return True elif A==0: # s*t=Bx+C。B>0もしくはC>0でOK。 return C>0 or B>0 else: # Ax^2+Bx+C=0が2つの異なる実数解を持っている必要がある。 if B*B<=4*A*C: return False # [p,q]の間でs*t>0→p>0かq>0でOK。 return (-B+(B*B-4*A*C)**0.5)/(2*A)>0 or (-B-(B*B-4*A*C)**0.5)/(2*A)>0 n=int(input()) for i in range(n): dat=list(map(int,input().split())) if solve(dat[3],dat[0],dat[4],dat[1],dat[5],dat[2]): print("YES") else: print("NO")
感想
今日katagaitaiなんですけど…絶起に気を付けましょう。