京都大学プログラミングコンテスト2012 practice: D - A mul B Problem

https://atcoder.jp/contests/kupc2012pr/tasks/kupc2012pr_4

問題概要

N行N列の行列A, B, Cが与えられる。AB = Cであるか判定せよ。

N <= 1000

解法

普通に掛け算するとO(N3)だが、検算だけなら各要素が0か1をランダムでとるN要素のベクトルrを使って ABr = Cr かを見るというO(N2)の乱択アルゴリズムがある(詳しくは以下の記事参照)。

https://research.preferred.jp/2011/01/matrix-multiplication-and-polynomial-identity/

これをやると AB=C なら必ず ABr = Cr で、AB!=C なら1/2で ABr != Cr となるので、100回ほど回して一度でも ABr != Cr となるかどうかを見れば十分な速度・精度で答えを得られる(上の記事の「AB≠C の時は確率(1/2)kでNoと出力する」は「確率1-(1/2)kでNoと出力する」の打ち間違い?)。

感想

なるほどね

コード

import std.stdio, std.array, std.string, std.conv, std.algorithm;
import std.typecons, std.range, std.random, std.math, std.container;
import std.numeric, std.bigint, core.bitop, core.stdc.string;

void main() {
    auto N = readln.chomp.to!int;
    auto A = iota(N).map!(_ => readln.split.map!(to!long).array).array;
    auto B = iota(N).map!(_ => readln.split.map!(to!long).array).array;
    auto C = iota(N).map!(_ => readln.split.map!(to!long).array).array;

    int yes = 0, no = 0;

    foreach (_; 0..100) {
        auto v = iota(N).map!(_ => uniform(0, 2).to!long).array;
        auto t = v.dup;
        auto u = new long[](N);

        foreach (i; 0..N) foreach (j; 0..N) {
            u[i] += B[i][j] * v[j];
        }
        v = u.dup;
        u[] = 0;

        foreach (i; 0..N) foreach (j; 0..N) {
            u[i] += A[i][j] * v[j];
        }
        v = u.dup;
        u[] = 0;

        foreach (i; 0..N) foreach (j; 0..N) {
            u[i] += C[i][j] * t[j];
        }
        t = u.dup;

        (v == t ? yes : no) += 1;
    }

    writeln(no ? "NO" : "YES");
}