2021icpc 济南 J Determinant(高斯消元 + 行列式求值)
题目来源:J Determinant
购买的时候将时光机取消即可
题意:
给出 n * n 的行列式的值的绝对值 det 以及这个行列式, 判断该行列式的值的正负, 其中 det 最多有1e4的位数
思路:
-
因为线性代数太久没看过了, 比赛的时候甚至都不知道 det 是行列式的值,还是一点一点推出来的, 发现 A的逆 乘 A 事实上就是A的第 i 列和第 j 列相乘, 所以化成上三角之后有用的部分只有主对角线的部分, 那么第 i 列和第 i 列相乘就是 A[i][i] * A[i][i], 那么最后就是相当于去找 行列式的值 的正负.
-
但是这个题中 det 最多有 1e4 的位数,所以肯定不能直接用高斯消元直接将行列式化成上三角的形式, 同时也不能只是用 double, 最后判正负号的个数, 因为有的地方也会爆 double.
-
我们需要取一个比较大的素数, 每次以取模的形式将行列式化成上三角的形式, 最后用行列式的值与 det % mod 的值去比较
- 若 det % mod == ans, 说明 det 是正的
- 否则, det % mod + ans 一定等于 n, det是负的, 比如 (-7 % 5 + 5) % 5 = 3, 7 % 5 = 2
-
需要注意的是, 使用高斯消元交换行列式的两行时, 行列式的正负会改变
AC代码
#include <bits/stdc++.h>
#define endl "\n"
#define rep(i, m, n) for (int i = (m); i <= (n); ++i)
#define rrep(i, m, n) for (int i = (m); i >= (n); --i)
#define IOS ios::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 110, mod = 1e9 + 7;
ll n, p[N][N], det;
string s;
ll fact() { // 将 字符串s 转化成 整形det
ll m = 0, l = s.length();
rep(i, 0, l - 1) {
m = m * 10 + s[i] - '0';
m %= mod;
}
return m;
}
ll qpow(int a, int b) {
ll res = 1; a %= mod;
while (b) {
if (b & 1) res = (ll)res * a % mod;
a = (ll)a * a % mod; b >>= 1;
}
return res;
}
ll inv(ll x) { return qpow(x, mod - 2); } // 逆元
ll guess() { // 高斯消元 求 行列式
ll ans = 1;
rep(i, 1, n) {
rep(j, i, n)
if (p[j][i]) { // 不能让 p[i][i] = 0, 即对角线的部分不能为0
rep(k, i, n) swap(p[i][k], p[j][k]);
if (i != j) ans = -ans;//交换两行,行列式正负改变
break;
}
// 用第 i 行去修改第 j 行
// p[j][k] = p[j][k] - p[i][k] * p[j][i] / p[i][i];
for (int j = i + 1, invf = inv(p[i][i]); j <= n; ++j) {
ll t = p[j][i] * invf % mod;
rrep(k, n, i) p[j][k] = ((p[j][k] - p[i][k] * t % mod) % mod + mod) % mod;
}
// 行列式的值就是化成上三角后主对角线的积乘上已经提取出来的数字
ans = (ans * p[i][i] % mod + mod) % mod;
}
return ans;
}
void solve() {
scanf("%lld", &n);
cin >> s; det = fact();
rep(i, 1, n) rep(j, 1, n)
scanf("%lld", &p[i][j]);
ll res = guess();
if (res == det) puts("+");
else puts("-");
}
int main() {
int t; cin >> t;
while (t--) solve();
return 0;
}