Условие
Симметричные квадратные матрицы [latex]A[/latex] и [latex]B[/latex] порядка [latex]n[/latex] заданы последовательностями из [latex]\frac{n(n+1)}{2}[/latex] чисел аналогично правым треугольным матрицам. Получить в аналогичном виде матрицу [latex]A^{2}-B^{2}[/latex].
Решение
Безусловно, по входным данным можно восстановить матрицы, тривиальным образом их перемножить и вывести результат в заданном виде. Как пример плохого стиля программирования: наивный алгоритм приводит к почти двукратному перерасходу потребляемой памяти и существенно уступает в производительности. Существует другое решение, прийти к которому можно путем последовательного приспособления стандартных матричных операций к формату входных данных.
Наибольшую сложность, как вычислительную, так и идейную, представляет умножение матриц, в данном случае — возведение в квадрат. Нижеприведенная последовательность шагов позволит перемножать симметричные матрицы, используя их линейное представление в формате исходных данных.
Замечание 1
Подпространство симметричных матриц незамкнуто относительно умножения: произведением двух симметричных матриц может быть несимметричная матрица.
Пример
[latex] \left(\begin{array}{ccc} 1 & 1 & 3 \\ 1 & 4 & 5 \\ 3 & 5 & 0\end{array} \right) \cdot \left( \begin{array}{ccc} 4 & 0 & 0 \\ 0 & 4 & 3 \\ 0 & 3 & 2 \end{array} \right) = \left( \begin{array}{ccc} 4 & 13 & 9 \\ 4 & 31 & 22 \\ 12 & 20 & 15 \end{array} \right)[/latex]
Замечание 2
Степень симметрической матрицы также является симметрической матрицей. Доказательство основано на представлении матрицы как представления линейного оператора и на свойствах эрмитовых операторов.
Во всех дальнейших выкладках подразумевается, что матрица представлена линейным массивом из [latex]\frac{n(n+1)}{2}[/latex] элементов.
Для начала, заметим, что элемент [latex]c_{i,j}[/latex] матрицы [latex]C=A^{2}[/latex], равен скалярному произведению (как векторов в стандартном базисе) [latex]i[/latex]-ой строки матрицы [latex]A[/latex] на [latex]j[/latex]-ую её строку (в силу того, что в симметричной матрице [latex]j[/latex]-ая строка совпадает с [latex]j[/latex]-м столбцом). Следовательно, для возведения в степень симметричной матрицы необходимо и достаточно реализовать операцию скалярного перемножения двух её строк.
Тогда следует понять, как по данному представлению матрицы получить её [latex]i[/latex]-ую строку. Для удобства, выпишем имеющиеся элементы в виде полной матрицы. Заметим, что первым элементом [latex]i[/latex]-ой строки будет [latex]i[/latex]-ый элемент первой строки, и обобщим это наблюдение. Обозначим позицию текущего интересующего нас элемента [latex]i[/latex]-ой строки как [latex]j[/latex]. Если [latex]j < i[/latex], то следует выбрать [latex]i[/latex]-ый элемент [latex]j[/latex]-ой строки, иначе следует выбрать все элементы [latex]j[/latex]-ой строки, лежащие правее данного. Графически можно интерпретировать алгоритм таким образом: начиная с [latex]i[/latex]-го элемента первой строки, спускаться вертикально вниз по матрице до тех пор, пока не будет достигнута [latex]i[/latex]-ая строка, далее — двигаться вправо до конца строки. Наглядность алгоритма позволяет легко воплотить его программно.
Следует только пронаблюдать, как изменяются расстояния между элементами, лежащими один под другим, при движении вниз по строкам матрицы, что позволит легко перенести алгоритм на линейное представление верхней треугольной матрицы. Предлагаем читателю самому проделать это несложное, но наглядное упражнение, для лучшего понимания алгоритма.
Теперь можно перейти непосредственно к реализации.
Тесты
[latex]n[/latex] | [latex]A[/latex] | [latex]B[/latex] | [latex]A^{2}-B^{2}[/latex] |
2 | 1 2 5 | 7 4 8 | -60 -48 -51 |
4 | 1 7 3 -2 3 1 9 2 8 11 | 4 5 11 -3 4 -7 22 1 4 0 | -108 116 -8 -79 -434 -10 75 -109 290 -239 |
Реализация
ideone: http://ideone.com/Dyte8l
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
#include <bits/stdc++.h> using namespace std; //Симметричная квадратная матрица порядка n задана линейным набором из //n(n+1)/2 элементов. Все методы класса оперируют только с таким представлением. template <typename T> class symmetric_matrix{ private: T* data; int data_size; public: //Конструкторы. symmetric_matrix(){} symmetric_matrix(int n){ data = new T[(n*(n+1))/2]; data_size = n; } symmetric_matrix(int n, int value){ data = new T[(n*(n+1))/2]; for(int i = 0; i < (n*(n+1))/2; i++) data[i] = value; data_size = n; } //Методы. int size(){ return data_size; } T get(int pos){ return data[pos]; } void set(int pos, T value){ data[pos] = value; } //Подпространство симметричных квадратные матриц заданного порядка //не замкнутно относительно операции матричного умножения. //Тем не менее, при возведение матрицы в степень замкнутость не //нарушается, в связи с чем операция умножения реализована для него. symmetric_matrix<T> operator *(symmetric_matrix<T> B){ int n = data_size; symmetric_matrix<T> C(n); int curr = 0; for(int row = 0; row < n; row++){ //row for(int col = row; col < n; col++){ //col int rpos = row, cpos = col; //(row, col) int rstep = n-1, cstep = n-1; //row, col step for(int i = 0; i < n; i++){ C.set(curr, C[curr] + data[rpos]*B[cpos]); rpos += (rstep >= n-row)*(rstep - 1) + 1; cpos += (cstep >= n-col)*(cstep - 1) + 1; rstep--, cstep--; } curr++; } } return C; } //Операторы. T operator [](int pos){return data[pos];} symmetric_matrix<T> operator +(symmetric_matrix<T> B){ int n = B.size(); symmetric_matrix<T> C(n); for(int i = 0; i < (n*(n+1))/2; i++) C.set(i, data[i] + B[i]); return C; }; symmetric_matrix<T> operator -(symmetric_matrix<T> B){ int n = B.size(); symmetric_matrix<T> C(n); for(int i = 0; i < (n*(n+1))/2; i++) C.set(i, data[i] - B[i]); return C; }; }; //Бинарный алгоритм возведения матрицы в степень. template <typename T> symmetric_matrix<T> power(symmetric_matrix<T> A, int n){ symmetric_matrix<T> result(A.size()); //Изначально result - нейтральный элемент относительно умножения //т.е. единичная матрица. for(int i = 0, diag = 0; i < A.size(); i++){ result.set(diag, 1); diag += A.size()-i; } while(n){ if(n & 1) result = result * A; A = A * A; n >>= 1; } return result; } int main(){ int n; cin >> n; symmetric_matrix<int> A(n), B(n), C(n); //Считывание данных for(int i = 0; i < (n*(n+1))/2; i++){ int value; cin >> value; A.set(i, value); } for(int i = 0; i < (n*(n+1))/2; i++){ int value; cin >> value; B.set(i, value); } C = power(A, 2) - power(B, 2); for(int i = 0; i < (n*(n+1))/2; i++){ cout << C.get(i) << " "; } return 0; } |
Тесты
Детали реализации
Задачи раздела носят обучающий характер, так что при реализации были произведены определенные обобщения, что делает решение более универсальным.
- Формулировка задачи не оговаривает тип элементов матрицы, так что класс [latex]symmetric\_matrix[/latex] является шаблоном, пригодным для типов данных, поддерживающих операции сложения, вычитания и умножения (соответствующие операторы определены внутри класса).
- Алгоритм бинарного возведения матрицы в степень полезен при решении более широкого круга задач, в связи с чем также реализован. При необходимости, программа легко обобщается на большие степени матрицы.
- Предусмотрены три типа конструкторов: конструктор по умолчанию и два параметрических конструктора, один из которых позволяет заполнить матрицу некоторым наперед заданным значением.
- Для удобства работы с заданным представлением данных, добавлен синтаксический сахар: перегружен оператор [latex][][/latex], осуществляющий доступ к заданному элементу матрицы в линейном её представлении.
В условии задачи 719б есть замечание (см. задачу 716). Значит в Вашем условии, Вам нужно было повторить соответствующую часть условия задачи 716.
А вот по решению можно писать очень много — Вы себе значительно усложнили задачу, но все же:
вместо
лучше
и тогда и set особо не нужен будет, можно будет писать A[i]=….;
вместо
лучше
чтобы избежать лишнего копирования.
как сами понимаете не очень корректен вообще, лучше сделать метод возведения в квадрат. Кроме того, в бинарном возведении в степень используется произведение разных матриц — при возведении в степень, отличную от степени двойки. Оно у Вас корректное?
«Предлагаем читателю самому проделать это несложное, но наглядное упражнение, для лучшего понимания алгоритма.» — вот это уже оригинально…
Честно говоря, нет желания вникать в достаточно сложное изменение индексов в строчках 38-48.
Простите, поправлю сам себя, правильно
Саму матрицу то мы не меняем.
Кстати, раз Вы задумали класс, выделяющий память динамически внутри своих методов (в конструкторе в Вашем случае), то нужен деструктор (new у Вас есть, а вот delete?), конструктор копирования и перегруженный operator=. Так что не все так просто в ООП на C++. Технического кода довольно много.
Попытку написать класс зачёл. С учётом всех замечаний, конечно.