yukicoder No.635 自然門松列

「3月中に毎日1問解いて黄色になろう!」キャンペーン4日目のつもり。Twitterで普通に呟いていましたが、射撃部の大会があったので普通に競プロはお休みしてCodinGameやってました(すっとぼけ)

今回の問題はガッチガチの高校数学で攻めました。

問題概要

門松列200本ノック。3本の木がt秒後にはxt+yの長さになっているので、門松列になる瞬間が存在するか判定せよ。

No.635 自然門松列 - yukicoder

解説

さて、取り敢えず分かりやすいように表記を変えて、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なんですけど…絶起に気を付けましょう。