B - Geometric Sequence Editorial by rsk0315

誤差で WA になるケースの構築に関して

分数を誤差なく比較する必要があります。 (公式解説 からの引用)

こうした話はしばしば目にしますが、実際に誤差が出るケースを見ないと「なんか誤差というものがあるらしい?」となってしまう人も多いでしょう。


次の単純な解法を考えます。

use proconio::input;
fn main() {
    input! {
        a: [f64],
    }
    let res = a.windows(2).all(|w| w[1] / w[0] == a[1] / a[0]);
    println!("{}", if res { "Yes" } else { "No" });
}

Yes/No の問題なので、次の 2 通りが考えられます。

  • 実際には Yes だが No を出力してしまう。
  • 実際には No だが Yes を出力してしまう。

まず、今回の問題においては前者のケースは存在しないことを示します。

正解が Yes であるとき、ある \(r\) が存在して、任意の \(i\) に対して \(\frac{a_{i+1}}{a_i} = r\) が成り立ちます。 \(10^9\) 以下の整数は誤差なく表せるため、任意の \(i\) に対して \(a_{i+1}\oslash a_i = \hat r\) となります。ここで、\(\hat r\)\(r\)f64 で表せるように丸めた値、\(x\oslash y\)\(\frac xy\)f64 で表せるように丸めた値とします。


以降、正解が No であるのに Yes と判定してしまうケースを考えます。\(\frac{a_{i+1}}{a_i} \ne \frac{a_2}{a_1}\) となる \(i\) が一つでも存在すれば十分なので、\(N=3\) としてよいです。

ある(これから考える)\(\varepsilon\) に対して \(\frac{a_2}{a_1} = \frac{a_3}{a_2} + \varepsilon\) とします。\(a_2^2=a_1a_3+a_1a_2\cdot\varepsilon\) です。\(a_1a_2\cdot\varepsilon=1\) としてみると、\((a_2-1)(a_2+1) = a_1a_3\) とできます。

ここで、\((a_1, a_3) = (a_2-1, a_2+1)\) としてみましょう。これにより、探索するべき変数が \(a_2\) 一つになったため、\(10^9\) 個程度であればすぐに調べられます。

実際、たとえば \(a_2 = 67114657\) とすると下記が成り立ちます。

\[ \begin{aligned} a_2\oslash a_1 &= a_3\oslash a_2 \\ &=1.000000014899875{\small 111495930468663}{\scriptsize 57326507568359375} \\ &= \texttt{0x10000003FFE960}\cdot 2^{-52}. \end{aligned} \]

なので、下記のケースによって WA にすることができます。

3
67114656 67114657 67114658

なお、たとえば \(a_2 = 999999807\) のケースでは、下記のように No と判定されます。

\[ \begin{aligned} a_2\oslash a_1 &= 1.000000001000000{\small 304784975924121}{\scriptsize 6816008090972900390625} \\ &\ne 1.000000001000000{\small 082740370999090}{\scriptsize 373516082763671875} \\ &= a_3\oslash a_2. \end{aligned} \]

今回は(たまたま)\(a_2 = 10^9-1\) のケースで WA にすることもできますが、誤差は容易に予測できるものではないので、「大きいケースを適当に入れてみてうまくいったから、たぶん整数型を使わなくても大丈夫であろう」のような判断はしないようにしましょう。

posted:
last update: