일부 선형 함수는 역함수가 존재합니다. 예를 들어, 어떤 각도만큼의 회전이 그렇습니다. 반면 역함수가 존재하지 않는 함수도 있습니다. 예를 들어, 사영이 그렇습니다. 가역성의 개념은 행렬로도 확장할 수 있습니다.
정의. 행렬 $A$가 가역이라는 것은 다음을 만족하는 행렬 $A^{-1}$이 존재한다는 것입니다.
$$ A \cdot A^{-1} = A^{-1} \cdot A = I $$행렬 곱셈의 성질에 따라, 역행렬이 존재하려면 행렬 $A$는 정사각 행렬이어야 하지만, 이것만으로 항상 충분하지는 않습니다.
#연립 선형 방정식
행렬의 가역성 개념은 선형 방정식을 푸는 데 중요합니다:
$$ Ax = b \leftrightarrow \begin{cases} a_{11} x_1 + a_{12} x_2 + \ldots + a_{1n} x_n = b_1 \\ a_{21} x_1 + a_{22} x_2 + \ldots + a_{2n} x_n = b_2 \\ \ldots \\ a_{n1} x_1 + a_{n2} x_2 + \ldots + a_{nn} x_n = b_n \end{cases} $$ 행렬 $A$가 가역이면 해는 유일하며, 그 해는 다음과 같습니다. $$ x = A^{-1} b $$그렇지 않으면 해가 없거나 무한히 많으며, 일반적으로 이 해들은 어떤 다차원 공간 위에 놓이게 됩니다. 이는 다음과 같이 생각하면 편리합니다. 각 조건(방정식 한 줄)은 $n$차원 공간에서 어떤 초평면을 정의하며, 이 초평면들의 교집합은 비어 있거나, 서로 일치하거나, 더 낮은 차원의 어떤 부분공간을 이룹니다.

연립일차방정식의 풀이는 수치 선형대수의 기본 문제 중 하나이며, 다른 알고리즘의 하위 문제로 자주 등장합니다.
#가우스 소거법
가우스 소거법(영어: Gaussian elimination)을 사용하면 연립일차방정식을 풀 수 있을 뿐만 아니라 역행렬을 구하고 관련 문제들을 해결할 수도 있습니다.
이 방법은 연립방정식에 대해 다음과 같은 연산을 자유롭게 할 수 있다는 사실에 기반합니다:
- 두 방정식의 위치를 서로 바꾸는 것,
- 임의의 방정식에 0이 아닌 상수를 곱합니다,
- 한 방정식에서 다른 방정식을 뺍니다.
이때 위의 모든 연산은 가역적이며 어떤 정보도 잃지 않습니다. 모든 행은 항상 서로의 자명하지 않은 선형 결합이 되고, 원래의 모든 방정식은 항상 어느 한 방정식에 어떤 계수와 함께 반영되기 때문입니다.
알고리즘의 목표는 이 세 가지 연산을 사용하여 연립방정식을 다음과 같은 형태로 만드는 것입니다.
$$ \begin{cases} 1 \cdot x_1 + 0 \cdot x_2 + \ldots + 0 \cdot x_n = b_1' \\ 0 \cdot x_1 + 1 \cdot x_2 + \ldots + 0 \cdot x_n = b_2' \\ \ldots \\ 0 \cdot x_1 + 0 \cdot x_2 + \ldots + 1 \cdot x_n = b_n' \end{cases} $$그러면 해는 단순히 $x = b’$가 됩니다.
#알고리즘
해가 존재하며 유일하다고 가정하고 진행하겠습니다.
행렬 $A$의 오른쪽에 벡터 $b$를 덧붙이고, 그 결과로 얻은 $n \times (n+1)$ 행렬을 반복적으로 원하는 형태로 변환해 나가겠습니다. $i$번째 단계에서는 셀 $a_{ii}$에 1이 오고, $i$번째 열의 나머지 모든 셀에는 0이 오도록 만들고자 합니다.
$i$번째 단계를 수행하려면 다음과 같이 합니다:
- $i$번째 열에서 0이 아닌 원소를 아무거나 하나 찾고, 그 원소가 있는 행을 $i$번째 행과 서로 바꿉니다;
- $i$번째 행 전체를 원소 $a_{ii}$로 나눕니다(이 원소는 0이 아닙니다. 바로 이전 단계에서 일부러 찾았기 때문입니다).
- 나머지 모든 행 $j \ne i$를 차례로 돌면서 $i$번째 행에 계수 $a_{ji}$를 곱한 것을 뺍니다.
그 결과 $i$번째 단계가 끝나면 원소 $a_{ii}$는 1이 되고(그 원소를 자기 자신으로 나누었기 때문입니다), $i$번째 열의 나머지 모든 칸은 0이 됩니다(그 위치들에서 $a_{ij} \cdot a_{ii} = a_{ij}$를 뺐기 때문입니다).
따라서 $n$번의 단계를 거치면 행렬을 원하는 형태로 만들 수 있으며, 덧붙인 마지막 $(n+1)$번째 열이 답이 됩니다.
#구현
계산 안정성을 고려하면 각 단계에서 0이 아닌 아무 원소나 피벗으로 택하는 것이 아니라 가장 큰 원소를 택하는 것이 합리적입니다. 결국 그 원소로 나누게 되는데, 부동소수점 수의 경우 0에 매우 가까운 수가 나올 수 있기 때문입니다.
typedef vector< vector<double> > matrix;
vector<double> gauss(matrix a) {
int n = (int) a.size();
for (int i = 0; i < n; i++) {
// находим опорный элемент
int best = i;
for (int j = i + 1; j < n; j++)
if (abs(a[j][i]) > abs(a[best][i]))
best = j;
// меняем строчки местами
swap(a[best], a[i]);
// нормализуем i-тую строчку (слева нули, с ними ничего делать не надо)
for (int j = n; j >= i; j--)
a[i][j] /= a[i][i];
// зануляем все остальные ячейки в i-том столбце
for (int j = 0; j < n; j++)
if (j != i)
// слева только нули, так что можно пройтись только по правой части
for (int k = n; k >= i; k--)
a[j][k] -= a[i][k] * a[j][i];
}
vector<double> ans(n);
for (int i = 0; i < n; i++)
ans[i] = a[i][n];
return ans;
}
이 알고리즘은 $O(n^3)$ 시간에 동작합니다.
또한 이 알고리즘은 실수뿐만 아니라 다른 체(field)로도 일반화할 수 있습니다. 예를 들어 소수 모듈로의 나머지에도 적용할 수 있으며, 이때는 나눗셈에 해당하는 적절한 절차를 사용하기만 하면 됩니다.
#불 행렬
올림피아드 맥락에서 연립 일차방정식을 풀어야 하는 문제 대부분은 실제로는 이를 체 $\mathbb{Z}_2$ 위에서, 즉 2를 법으로 하여 풀어야 합니다.
문제. $n$개의 전구와 $n$개의 스위치가 있습니다. 활성화된 각 스위치는 전구들의 어떤 부분집합의 상태를 바꿉니다(켜거나 끕니다). 모든 전구의 상태가 알려져 있을 때, 스위치들의 상태를 복원해야 합니다.
결국 다음 연립방정식을 풀라는 것입니다:
$$ \begin{cases} a_{11} x_1 + a_{12} x_2 + \ldots + a_{1n} x_n \equiv b_1 \pmod 2\\ a_{21} x_1 + a_{22} x_2 + \ldots + a_{2n} x_n \equiv b_2 \pmod 2\\ \ldots \\ a_{n1} x_1 + a_{n2} x_2 + \ldots + a_{nn} x_n \equiv b_n \pmod 2 \end{cases} $$여기서 $x$는 스위치의 상태, $b$는 전구의 상태, $A$는 스위치가 전구에 영향을 주는지에 대한 정보입니다.
이 경우 비트셋을 사용하여 일반적인 가우스 소거법을 크게 가속하고 단순화할 수 있습니다:
typedef bitset<maxn> t;
typedef array<t, maxn> matrix;
t gauss(matrix a) {
for (int i = 0; i < n; i++) {
int nonzero = i;
for (int j = i + 1; j < n; j++)
if (a[j][i])
nonzero = j;
swap(a[nonzero], a[i]);
for (int j = 0; j < n; j++)
if (j != i && a[j][i])
a[j] ^= a[i];
}
t x;
for (int i = 0; i < n; i++)
x[i] = a[i][n] ^ a[i][i];
return x;
}
이번 구현과 이전 구현 모두에서 해가 존재하며 유일하다고 가정했다는 점에 주의하십시오. 그렇지 않다면 어느 시점에서 피벗으로 쓸 0이 아닌 원소를 찾지 못하게 됩니다.
이 경우 완전히 0인 열은 무시하고 알고리즘을 계속 실행할 수 있으며, 그러면 마지막에 어떤 $b_i’$와 같은 0인 합이 몇 개 남게 됩니다. 이 $b_i’$가 모두 0이면 해가 존재하고, 그렇지 않으면 해가 존재하지 않습니다.
“실제 세계"에서 온 입력 데이터의 경우 계수에 작은 노이즈 $\epsilon_{ij}$를 더할 수 있습니다. 이렇게 하면 해가, 그것도 유일한 해가 항상 존재함을 보장할 수 있습니다.
#기저
정의. 벡터 집합의 기저란 선형 결합을 통해 그 집합의 모든 벡터를, 그리고 오직 그 벡터들만을 유일한 방법으로 표현할 수 있는 벡터들의 모음을 말합니다.
기저는 선형대수학에만 있는 것이 아닙니다. 예를 들어, ${1, x, x^2}$는 모든 이차 다항식의 기저입니다. 또는 ${\neg, \land, \lor}$는 모든 논리식의 기저입니다(즉, 모든 것을 AND, OR, NOT으로 표현할 수 있습니다). 임의의 프로그래밍 언어에서도 무엇이든 작성할 수 있게 해 주는 어떤 명령어 집합을 골라낼 수 있으며, 이 집합 역시 이러한 의미에서 기저라고 할 수 있습니다.
정의. 벡터계 ${a_1, a_2, \dots, a_n}$에 대하여, 적어도 하나의 $i$에 대해 $\lambda_i \neq 0$이고 다음 식을 만족하는 실수 계수의 조합 $(\lambda_1, \lambda_2, \dots, \lambda_n)$이 존재하면, 이 벡터계를 선형 종속이라고 합니다.
$$ \lambda_1 \cdot a_1 + \lambda_2 \cdot a_2 + \dots + \lambda_n \cdot a_n = 0 $$그렇지 않은 경우 이 시스템을 선형 독립이라고 합니다.
명제. $n$차원 공간의 기저는 $n$개의 벡터로 이루어진 선형 독립인 집합입니다.
증명. 기저가 선형 종속이라고 합시다. 그러면 길이가 0인 벡터를 적어도 두 가지 방법으로 표현할 수 있는데, 이는 정의에 모순됩니다.
올림피아드 문제에서는 기저를 찾거나 유지해야 하는 경우가 많으며, 흔히 “각 벡터에 어떤 비용이 있을 때 최대 가중치 기저를 구성하라"와 같은 형태입니다. 벡터 집합이 기저인지 확인하려면 이 벡터들로 영벡터를 자명하지 않은 방식으로 표현할 수 있는지 확인하면 되며, 이는 가우스 소거법으로 할 수 있습니다.
연습 문제. $0$부터 $(2^{30}-1)$까지의 정수 $10^5$개로 이루어진 배열이 주어집니다. 이 배열의 서로 다른 부분 수열 중 xor-합이 주어진 수 $x$와 같은 것의 개수를 구하십시오.