neras_1215
@neras_1215 (おーやま)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

jsで二項係数のmodの計算が合わない

二項係数のmodの計算が合わない

以下問題を解いています。

発生している問題・エラー

jsを用いて、拡張Euclide互除法で逆元を計算しています。
大きい数字だと答えが合わず、どこが悪いのかわかりません。。

function main($) {
    z = $.split(' ').map(Number)
    let x, y;
    [x, y] = z
    if ((x + y) % 3 !== 0) {
        console.log(0)
        return
    }
    b = (2 * x - y) / 3
    a = x - 2 * b
    if (a < 0 || b < 0) {
        console.log(0)
        return
    }

    mod=1e9+7, max=a+b+1
    fac=Array(max), inv=Array(max), finv=Array(max)
    pre=()=>{
        fac[0]=fac[1]=1,inv[1]=1,finv[0]=finv[1]=1
        for (i=2;i<max;i++) {
            fac[i]=fac[i-1]*i%mod
            inv[i]=mod-inv[mod%i]*(mod/i|0)%mod
            finv[i]=finv[i-1]*inv[i]%mod
        }
    }
    modC=(n,k)=>{if(n<k)return 0;if(n<0||k<0)return 0;return fac[n]*(finv[k]*finv[n-k]%mod)%mod}
    pre()

    console.log(modC(a+b, a))

}
main(require('fs').readFileSync('/dev/stdin', 'utf8'))

問題のサンプルでテストした際のエラー

[INFO] online-judge-tools 11.5.1 (+ online-judge-api-client 10.10.1)
[INFO] 3 cases found

[INFO] sample-1
[INFO] time: 0.032846 sec
[SUCCESS] AC

[INFO] sample-2
[INFO] time: 0.028956 sec
[SUCCESS] AC

[INFO] sample-3
[INFO] time: 0.116753 sec
[FAILURE] WA
input:
999999_999999

output:
599949243

expected:
151840682


[INFO] slowest: 0.116753 sec  (for sample-3)
[INFO] max memory: 66.224000 MB  (for sample-3)
[FAILURE] test failed: 2 AC / 3 cases

0

3Answer

↓こんなに小さいマスでも答えが合いません。

入力例

6 6

出力例(AC)

6

このロジックで必要とする整数の桁(精度)が、javascriptでは、足りてないことが原因だと思います。

javascript
> a = 500000004
500000004
> b = a * a
250000004000000000
> Number.MAX_SAFE_INTEGER 
9007199254740991
python
>>> a = 500000004
>>> b = a * a
>>> b
250000004000000016
swift
1> let a = 500000004 
a: Int = 500000004
2> let b = a * a 
b: Int = 250000004000000016
3> let c = Int.max
c: Int = 9223372036854775807
3Like

Comments

  1. @neras_1215

    Questioner

    わぁ。。。そうなのですね、ありがとうございます!
    js好きでしたが、これを機にC++に乗り換えようと思います。。
    コード自体はあってそうで安心しました、たいへん助かりました!

  2. コードは合っています。BigIntに変えたら、正しい答えが出るようになりました。

    BigInt
    > a = 500000004n
    500000004n
    > b = a * a
    250000004000000016n
    
    BigInt版
    function main($) {
        z = $.split(' ').map(Number)
        let x, y;
        [x, y] = z
        if ((x + y) % 3 !== 0) {
            console.log(0)
            return
        }
        b = (2 * x - y) / 3
        a = x - 2 * b
        if (a < 0 || b < 0) {
            console.log(0)
            return
        }
    
        mod=BigInt(1e9+7), max=a+b+1
        fac=Array(max), inv=Array(max), finv=Array(max)
        pre=()=>{
            fac[0]=fac[1]=BigInt(1),inv[1]=BigInt(1),finv[0]=finv[1]=BigInt(1)
            for (i=2;i<max;i++) {
                fac[i]=fac[i-1]*BigInt(i)%mod
                inv[i]=mod-inv[mod%BigInt(i)]*(mod/BigInt(i))%mod
                finv[i]=finv[i-1]*inv[i]%mod
            }
        }
        modC=(n,k)=>{if(n<k)return 0n;if(n<0||k<0)return 0n;return fac[n]*(finv[k]*finv[n-k]%mod)%mod}
        pre()
    
        console.log(modC(a+b, a).toString())
    
    }
    main(require('fs').readFileSync('/dev/stdin', 'utf8'))
    
  3. @neras_1215

    Questioner

    ありがとうございます!!🙇‍♀️💦
    型宣言が要らなくて楽な分、longなどの対応を表現すると
    ごちゃごちゃしてしまいますね…

1
2
4
8
2 14
3
x
4 x
x
5 x

ナイト(桂馬)の行動パターンは上記の通りです。

左下の数値は正方形3x3の時ナイトの置ける箇所の数です。

正方形のサイズをnとするとx=(n+1)/2で求めることができます。但し、nは奇数であること。

2,3,4,5....と規則性を発見できました。
但し、正方形に限ってです。

■段数の規則

  1. (1+1)/2=1
  2. (3+1)/2=2
  3. (5+1)/2=3
  4. (7+1)/2=4
  5. (9+1)/2=5

一方、右上の数値はスタート地点0,0からの経路パターン数です。1,2,4,8,14...にはどのような規則性があるのでしょうか?

経路パターン数をxとします。

■パターン数の規則(その1)

  1. 0+(1-0)+0x2=1
  2. 1+(2-1)+0x2=2
  3. 2+(2-2)+1x2=4
  4. 4+(2-2)+2x2=8
  5. 8+(2-2)+3x2=14

1項の0,1,2,4,8は一つ前のパターン数
2項の(2-2)は今の◯の数から前の◯の数を引いた数
3項の2x2は今の●の数にx2下数

■●の数は段数より◯の数2を引く

  1. 例外
  2. 2-2
  3. 3-2
  4. 4-2
  5. 5-2

◯は段数に関係なく2
●は段数-◯の数

■パターン数の規則(その2)
 前数 前◯ 前●

  1. 1 + 0x1 + 0x2 =1 例外(初期値は1)
  2. 1 + 1x1 + 0x2 =2
  3. 2 + 2x1 + 0x2 =4
  4. 4 + 2x1 + 1x2 =8
  5. 8 + 2x1 + 2x2 =14

1項の0,1,2,4,8は一つ前のパターン数
2項の(2x1)は一つ前◯の数x1
3項の(2x2)は一つ前●の数x2

■長方形ゴール問題

◯g
◯a
◯b
●g
●a
●b
◆G

奇数x奇数の正方形の場合、3x3,5x5はゴールがないが、7x7にはゴール◆Gがあります。
つまり、◯aー●a, ◯bー●b, ◯gー●gー◆Gの様に3つ飛びの行動パターンがあります。

以上から縦横のx,yの数でゴールの有無を判定できると期待してます。

2Like

浮動小数点数の誤差が原因です。

JavaScriptのNumberの実体は、IEEE754のbinary64形式であり、10進で16桁弱の精度しかありません。(以下のリンク先の「十進換算桁数」参照)

invの計算を追えば、何が起こっているか分かると思います。

まず、inv[1]1で初期化されています。

inv[1] = 1

次に、inv[2]は以下の計算で500000004となります。

inv[2] = mod - inv[mod%2] * (mod/2|0) % mod
       = 1000000007 - 1 * 500000003 % 1000000007
       = 1000000007 - 500000003 % 1000000007
       = 1000000007 - 500000003
       = 500000004

次のinv[3]の計算で問題が出ます。
本来であれば、以下のように計算されて、333333336になるはずです。

inv[3] = mod - inv[mod%3] * (mod/3|0) % mod
       = 1000000007 - 500000004 * 333333335 % 1000000007
       = 1000000007 - 166666668833333340 % 1000000007
       = 1000000007 - 666666671
       = 333333336

しかし、途中の計算で現れる166666668833333340は18桁もあるので、Numberでは正確に表現できず、Numberで表現できる最も近い値である166666668833333344に丸められてしまいます。
そのまま計算を続けると、333333332という誤った値となってしまいます。

inv[3] = 1000000007 - 500000004 * 333333335 % 1000000007
       = 1000000007 - 166666668833333344 % 1000000007
       = 1000000007 - 666666675
       = 333333332
1Like

Comments

  1. @neras_1215

    Questioner

    やはりlongのある言語の方が良さそうですね、、
    具体的に教えていただけて大変助かります、この様子だと逆元の計算自体が
    二分累乗使ったフェルマーの小定理だとしてもこのオーダーじゃ無理そうですよね
    大人しくC++勉強します😭

Your answer might help someone💌