有限体上での連立方程式(ガウスの消去法)
コンテンツ
有限体上で連立方程式を解くプログラム(ガウスの消去法の実装).
- 基本は実数体上のガウスの消去法と同じ.
- 除算を有限体での逆元にするだけ(拡張ユークリッドの互除法).
- ランク落ち?対策は思ったより簡単だった.
例えば,2元体上で
1 1 1 1 1 1 0 1 0 0 1 0
,つまり x1 + x2 + x3 = 1 x1 + x2 = 1 x3 = 0 は掃き出すと
1 1 1 1 0 0 1 0 0 0 0 0
となり,上三角ではなくなるが,もちろん解は存在する(x1 = 1, x2 = x3 = 0) .
- これを使えば,FlipItの解の存在判定が解ける.
以下はガウスの消去法本体. Ax = b (mod q) を解くのが目的. a は係数行列,つまり a = [A | b]である. x は解を記録する配列. q は素数で有限体の位数. 連立方程式に解が存在するとき,Trueを返し,解無しの場合Falseを返す.
// 有限体上の線型方程式系 Ax = b (mod q)を解く // a = [A | b]: m × n の係数行列 // x: 解を記録するベクトル // 計算量: O(min(m, n) * m * n) bool gauss(int **a, int *x, int m, int n, int q) { int rank = 0, *pivot = new int [n]; // 前進消去 for (int i = 0, j = 0; i < m && j < n-1; ++j) { int p = -1, tmp = 0; // ピボットを探す for (int k = i; p < 0 && k < m; ++k) { if (a[k][j] != 0) p = k; // 有限体上なので非零で十分 } // ランク落ち対策 if (p == -1) continue; // 第i行と第p行を入れ替える for (int k = j; k < n; ++k) tmp = a[i][k], a[i][k] = a[p][k], a[p][k] = tmp; // 第i行を使って掃き出す for (int k = i+1; k < m; ++k) { tmp = - a[k][j] * invMod(a[i][j], q) % q; for (int l = j; l < n; ++l) a[k][l] += tmp * a[i][l]; } // 第i行を正規化: a[i][j] = 1 にする tmp = invMod(a[i][j], q); for (int k = j; k < n; ++k) a[i][k] = a[i][k] * tmp % q; pivot[i++] = j, rank++; } // 解の存在のチェック for (int i = rank; i < m; ++i) if (a[i][n-1] != 0) return false; // 解をxに代入(後退代入) for (int i = 0; i < rank; ++i) x[i] = a[i][n-1]; for (int i = rank-1; i >= 0; --i) { for (int j = pivot[i] + 1; j < n-1; ++j) x[i] -= a[i][j] * x[j]; x[i] -= x[i] / q * q, x[i] = (x[i] + q) % q; // 0 <= x[i] < q に調整 } return true; }
# なんか有限体ではなく素体というべきなのかもしれないと,いまさらながら思いはじめる. ソースコード全体.適当なテストコードを含む.
#include <iostream> #include <cmath> using namespace std; void show(int **a, int m, int n); // input : a, b // output : x, y s.t. ax + by = (符号付き)gcd(a, b) int extGcd( int a, int b, int& x, int& y ) { if ( b == 0 ) { x = 1; y = 0; return a; } int g = extGcd( b, a % b, y, x ); y -= (a / b) * x; return g; } // xn = 1 (mod p) int invMod(int n, int p) { int x, y, g = extGcd ( n, p, x, y ); if (g == 1) return x; else if (g == -1) return -x; else return 0; // gcd(n, p) != 1,解なし } // 有限体上の線型方程式系 Ax = b (mod q)を解く // a = [A | b]: m × n の係数行列 // x: 解を記録するベクトル // 計算量: O(min(m, n) * m * n) bool gauss(int **a, int *x, int m, int n, int q) { int rank = 0, *pivot = new int [n]; // 前進消去 for (int i = 0, j = 0; i < m && j < n-1; ++j) { int p = -1, tmp = 0; // ピボットを探す for (int k = i; p < 0 && k < m; ++k) { if (a[k][j] != 0) p = k; // 有限体上なので非零で十分 } // ランク落ち対策 if (p == -1) continue; // 第i行と第p行を入れ替える for (int k = j; k < n; ++k) tmp = a[i][k], a[i][k] = a[p][k], a[p][k] = tmp; // 第i行を使って掃き出す for (int k = i+1; k < m; ++k) { tmp = - a[k][j] * invMod(a[i][j], q) % q; for (int l = j; l < n; ++l) a[k][l] += tmp * a[i][l]; } // 第i行を正規化: a[i][j] = 1 にする tmp = invMod(a[i][j], q); for (int k = j; k < n; ++k) a[i][k] = a[i][k] * tmp % q; pivot[i++] = j, rank++; } // 解の存在のチェック for (int i = rank; i < m; ++i) if (a[i][n-1] != 0) return false; // 解をxに代入(後退代入) for (int i = 0; i < rank; ++i) x[i] = a[i][n-1]; for (int i = rank-1; i >= 0; --i) { for (int j = pivot[i] + 1; j < n-1; ++j) x[i] -= a[i][j] * x[j]; x[i] -= x[i] / q * q, x[i] = (x[i] + q) % q; // 0 <= x[i] < q に調整 } return true; } int main() { const int m = 4, n = 5; int **a, *x; a = new int* [m]; for (int i = 0; i < m; ++i) a[i] = new int[n]; a[0][0] = 1, a[0][1] = 1, a[0][2] = 1, a[0][3] = 0, a[0][4] = 1; a[1][0] = 1, a[1][1] = 1, a[1][2] = 0, a[1][3] = 1, a[1][4] = 0; a[2][0] = 1, a[2][1] = 0, a[2][2] = 1, a[2][3] = 1, a[2][4] = 0; a[3][0] = 0, a[3][1] = 1, a[3][2] = 1, a[3][3] = 1, a[3][4] = 1; x = new int[n-1]; show(a, m, n); gauss(a, x, m, n, 2); show(a, m, n); for (int i = 0; i < n-1; ++i) cout << x[i] << " "; cout << endl; } void show(int **a, int m, int n) { cout << "==============================" << endl; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) cout << a[i][j] << " "; cout << endl; } cout << "==============================" << endl; }
作成者 Toru Mano
最終更新時刻 2023-01-01 (c70d5a1)