|
Курсовая работа: Багатокритеріальна задача лінійного програмування'тільки від однокритеріальної задачі ЛП (з одною функцією мети). '+ 'Всього функцій мети: '; sc_CantChangeStateInSolving= ': не можу міняти режим під час розв''язування…'; sc_CantDetMenuItem=': не визначено пункт меню, який викликав процедуру…'; sc_UnknownObjectCall=': невідомий об''єкт, який викликав процедуру: клас '; sc_NoCellOrNotSupported=': комірка не підтримується або не існує: '; sc_Row='Рядок'; sc_Col='Стовпець'; sc_CantOpenFile=': не можу відкрити файл: «'; sc_EmptyFileOrCantRead=': файл пустий або не читається: «'; sc_FileNotFullOrHasWrongFormat=': файл не повний або не того формату: «'; sc_CantReadFile=': файл не читається: «'; sc_CantCreateFile=': не можу створити файл: «'; sc_CantWriteFile=': файл не вдається записати: «'; sc_CurRowNotMarkedAsDestFunc= ': заданий рядок не помічений як функція мети: рядок '; sc_RowNumsIsOutOfTable=': задані номери рядків виходять за межі таблиці!..'; sc_NoDestFuncs=': немає рядків функцій мети! Задачу не розумію…'; sc_OnlyDestFuncsPresent=': у таблиці всі рядки є записами функцій мети!..'; sc_ForDestFunc=': для функції: '; sc_SearchingMin='шукаю мінімум'; sc_SearchingMax='шукаю максимум'; sc_CalculatingNoOptMeasures=': підраховую міри неоптимальності…'; sc_AllMeasurIsZero=': усі міри рівні нулю, додаю до них одиницю…'; sc_UniqueMeasureCantSetZero=': є тільки одна міра оптимальності (і одна'+ ' функція мети). Максимальна за модулем – вона ж. Додавання цієї'+ ' максимальної величини замінить її на нуль. Тому заміняю на одиницю…'; sc_WeightCoefs='Вагові коефіцієнти (Li[Func]=ui/W(U)):'; sc_ComprVarVals='Компромісні значення змінних'; sc_DestFuncComprVals='Компромісні значення функцій мети:'; Function ValSign (Const Value:TWorkFloat):TSignVal; overload; Var Res1:TSignVal; Begin Res1:=bc_Zero; If Value<0 then Res1:=bc_Negative Else if Value>0 then Res1:=bc_Positive; ValSign:=Res1; End; Function ValSign (Const Value:TValOrName):TSignVal; overload; Var Res1:TSignVal; Begin If Value. ElmType=bc_Number then Res1:=ValSign (Value. AsNumber) Else Begin If Pos (sc_Minus, Value. AsVarName)=1 then Res1:=bc_Negative Else Res1:=bc_Positive; End; ValSign:=Res1; End; Function GetValOrNameAsStr (Const Value:TValOrName):String; Begin If Value. ElmType=bc_Number then GetValOrNameAsStr:=FloatToStr (Value. AsNumber) Else GetValOrNameAsStr:=Value. AsVarName; End; Procedure DeleteFromArr (Var SArr:TValOrNameMas; Index, Count: Integer); overload; {Процедура для видалення з одновимірного масиву чисел чи назв змінних SArr одного або більше елементів, починаючи з елемента з номером Index. Видаляється Count елементів (якщо вони були у масиві починаючи із елемента з номером Index).} Var CurElm: Integer; Begin If Count<=0 then Exit; {якщо немає елементів для видалення} {Якщо є хоч один елемент із заданих для видалення:} If Length(SArr)>=(Index+1) then Begin {Якщо у масиві немає так багато елементів, скільки холіли видалити, то коригуємо кількість тих, що видаляємо:} If (Index+Count)>Length(SArr) then Count:=Length(SArr) – Index; {Зсуваємо елементи масиву вліво, що залишаються справа після видалення заданих:} For CurElm:=Index to (Length(SArr) – 1-Count) do SArr[CurElm]:=SArr [CurElm+Count]; {Видаляємо з масиву зайві елементи справа:} SetLength (SArr, Length(SArr) – Count); End; End; Procedure DeleteFromArr (Var SArr:TFloatArr; Index, Count: Integer); overload; {Процедура для видалення з одновимірного масиву дійсних чисел SArr одного або більше елементів, починаючи з елемента з номером Index. Видаляється Count елементів (якщо вони були у масиві починаючи із елемента з номером Index).} Var CurElm: Integer; Begin If Count<=0 then Exit; {якщо немає елементів для видалення} {Якщо є хоч один елемент із заданих для видалення:} If Length(SArr)>=(Index+1) then Begin {Якщо у масиві немає так багато елементів, скільки холіли видалити, то коригуємо кількість тих, що видаляємо:} If (Index+Count)>Length(SArr) then Count:=Length(SArr) – Index; {Зсуваємо елементи масиву вліво, що залишаються справа після видалення заданих:} For CurElm:=Index to (Length(SArr) – 1-Count) do SArr[CurElm]:=SArr [CurElm+Count]; {Видаляємо з масиву зайві елементи справа:} SetLength (SArr, Length(SArr) – Count); End; End; Procedure DelColsFromMatr (Var SDMatrix:TFloatMatrix; ColIndex, Count: Integer); {Процедура для видалення із матриці дійсних чисел SHeadArr одного або більше стовпців, починаючи зі стовпця з номером ColIndex. Видаляється Count стовпців (якщо вони були у матриці починаючи зі стовпця з номером ColIndex).} Var CurRow: Integer; Begin If Count<=0 then Exit; {якщо немає елементів для видалення} {Видаляємо елементи у вказаних стовпцях з кожного рядка. Так видалимо стовпці:} For CurRow:=0 to (Length(SDMatrix) – 1) do Begin DeleteFromArr (SDMatrix[CurRow], ColIndex, Count); End; End; Procedure DelRowsFromMatr (Var SDMatrix:TFloatMatrix; RowIndex, Count: Integer); {Процедура для видалення із матриці дійсних чисел SHeadArr одного або більше рядків, починаючи з рядка з номером RowIndex. Видаляється Count рядків (якщо вони були у матриці починаючи з рядка з номером RowIndex).} Var CurElm: Integer; Begin If Count<=0 then Exit; {якщо немає елементів для видалення} {Якщо є хоч один рядок із заданих для видалення:} If Length(SDMatrix)>=(RowIndex+1) then Begin {Якщо у матриці немає так багато рядків, скільки холіли видалити, то коригуємо кількість тих, що видаляємо:} If (RowIndex+Count)>Length(SDMatrix) then Count:=Length(SDMatrix) – RowIndex; {Зсуваємо рядки матриці вгору, що залишаються знизу після видалення заданих:} For CurElm:=RowIndex to (Length(SDMatrix) – 1-Count) do SDMatrix[CurElm]:=SDMatrix [CurElm+Count]; {Видаляємо з матриці зайві рядки знизу:} SetLength (SDMatrix, Length(SDMatrix) – Count); End; End; Procedure ChangeSignForValOrVarName (Var SDValOrName:TValOrName); {Зміна знаку числа або перед іменем змінної:} Begin If SDValOrName. ElmType=bc_Number then {для числа:} SDValOrName. AsNumber:=-SDValOrName. AsNumber Else {для рядка-назви:} Begin If Pos (sc_Minus, SDValOrName. AsVarName)=1 then Delete (SDValOrName. AsVarName, 1, Length (sc_Minus)) Else SDValOrName. AsVarName:=sc_Minus+SDValOrName. AsVarName; End; End; {Жорданове виключення за заданим розв'язувальним елементом матриці:} Function TGridFormattingProcs.GI (RozElmCol, RozElmRow: Integer; Var SDHeadRow, SDHeadCol:TValOrNameMas; Var SDMatrix:TFloatMatrix; Var DColDeleted: Boolean; ToDoMGI: Boolean=False; {прапорець на модифіковане Жорданове виключення} ToDelColIfZeroInHRow: Boolean=True):Boolean; {Функція виконує Жорданове виключення для елемента матриці SDMatrix з координатами (RozElmCol, RozElmRow). Окрім обробки матриці, здійснюється заміна місцями елементів у рядку і стовпцю-заголовках матриці (SDHeadRow, SDHeadCol). Вхідні дані: RozElmCol – номер стовпця матриці, у якому лежить розв'язувальний елемент. нумерація з нуля; RozElmRow – номер рядка матриці, у якому лежить розв'язувальний елемент. нумерація з нуля. Розв'язувальний елемент не повинен бути рівним нулю, інакше виконання Жорданового виключення не можливе; SDHeadRow, SDHeadCol – рядок і стовпець-заголовки матриці. Рядок-заголовок SDHeadRow повинен мати не менше елементів, ніж є ширина матриці. Він містить множники. Стовпець-заголовок SDHeadCol повинен бути не коротшим за висоту матриці. Він містить праві частини рівнянь (чи нерівностей) системи. Рівняння полягають у тому що значення елементів стовпця-заголовка прирівнюються до суми добутків елементів відповідного рядка матриці і елементів рядка-заголовка. Елементи у цих заголовках можуть бути числами або рядками-іменами змінних. Якщо довжина рядка-заголовка менша за ширину або стовпця-заголовка менша за висоту матриці, то частина комірок матриці, що виходять за ці межі, буде проігнорована; SDMatrix – матриця, у якій виконується Жорданове виключення; ToDoMGI – прапорець, що вмикає режим модифікованого Жорданового виключення (при ToDoMGI=True здійснюється модифіковане, інакше – звичайне). Модифіковане Жорданове виключення використовується для матриці, у якій було змінено знак початкових елементів, і змінено знаки елементів- множників у рядку-заголовку. Використовується для симплекс-методу. ToDelColIfZeroInHRow – прапорець, що вмикає видалення стовпця матриці із розв'язувальним елементом, якщо після здійснення жорданівського виключення у рядок-заголовок зі стовпця-заголовка записується число нуль. Вихідні дані: SDHeadRow, SDHeadCol – змінені рядок та стовпець-заголовки. У них міняються місцями елементи, що стоять навпроти розв'язувального елемента (у його стовпці (для заголовка-рядка) і рядку (для заголовка-стовпця). У заголовку-рядку такий елемент після цього може бути видалений, якщо він рівний нулю і ToDelColIfZeroInHRow=True. Тобто Жорданове виключення змінює ролями ці елементи (виражає один через інший у лінійних рівняннях чи нерівностях); SDMatrix – матриця після виконання Жорданового виключення; DColDeleted – ознака того, що при виконанні Жорданового виключення був видалений розв'язувальний стовпець із матриці (у його комірці у рядку-заголовку став був нуль). Функція повертає ознаку успішності виконання Жорданового виключення. } Var CurRow, CurCol, RowCount, ColCount: Integer; SafeHeadElm:TValOrName; MultiplierIfMGI:TWorkFloat; CurMessage: String; Begin {Визначаємо кількість рядків і стовпців, які можна обробити:} RowCount:=Length(SDMatrix); If RowCount<=0 then Begin GI:=False; Exit; End; ColCount:=Length (SDMatrix[0]); If Length(SDHeadCol)<RowCount then RowCount:=Length(SDHeadCol); If Length(SDHeadRow)<ColCount then ColCount:=Length(SDHeadRow); If (RowCount<=0) or (ColCount<=0) then Begin GI:=False; Exit; End; {Перевіряємо наявність розв'язуючого елемента у матриці (за координатами):} If (RozElmCol>(ColCount-1)) or (RozElmRow>(RowCount-1)) then Begin CurMessage:=sc_InvCoordsOfResolvingElm+': ['+IntToStr (RozElmCol+1)+';'+ IntToStr (RozElmRow+1)+']'+sc_CrLf+ sc_MatrixSize+': ['+IntToStr(ColCount)+';'+IntToStr(RowCount)+']'; If Self. CurOutConsole<>Nil then Self. CurOutConsole. Lines. Add(CurMessage); MessageDlg (CurMessage, mtError, [mbOk], 0); GI:=False; Exit; End; {Якщо розв'язуючий елемент рівний нулю, то виконати Жорданове виключення неможливо:} If SDMatrix [RozElmRow, RozElmCol]=0 then Begin CurMessage:=sc_ZeroResolvingElm+': ['+IntToStr (RozElmCol+1)+';'+ IntToStr (RozElmRow+1)+']='+FloatToStr (SDMatrix[RozElmRow, RozElmCol]); If Self. CurOutConsole<>Nil then Self. CurOutConsole. Lines. Add(CurMessage); MessageDlg (CurMessage, mtError, [mbOk], 0); GI:=False; Exit; End; {Виконуємо Жорданове виключення у матриці:} {Обробляємо усі елементи матриці, що не належать до рядка і стовпця розв'язуючого елемента:} For CurRow:=0 to RowCount-1 do For CurCol:=0 to ColCount-1 do If (CurRow<>RozElmRow) and (CurCol<>RozElmCol) then Begin SDMatrix [CurRow, CurCol]:= (SDMatrix [CurRow, CurCol]*SDMatrix [RozElmRow, RozElmCol] – SDMatrix [CurRow, RozElmCol]*SDMatrix [RozElmRow, CurCol]) / SDMatrix [RozElmRow, RozElmCol]; End; {+1, якщо задано зробити звичайне Жорданове виключення; -1 – якщо задано модифіковане:} MultiplierIfMGI:=(1–2*Abs (Ord(ToDoMGI))); {Елементи стовпця розв'язуючого елемента (окрім його самого) ділимо на розв'язуючий елемент:} For CurRow:=0 to RowCount-1 do If CurRow<>RozElmRow then SDMatrix [CurRow, RozElmCol]:=MultiplierIfMGI*SDMatrix [CurRow, RozElmCol]/ SDMatrix [RozElmRow, RozElmCol]; {Елементи рядка розв'язуючого елемента (окрім його самого) ділимо на розв'язуючий елемент з протилежним знаком:} For CurCol:=0 to ColCount-1 do If CurCol<>RozElmCol then SDMatrix [RozElmRow, CurCol]:=-MultiplierIfMGI*SDMatrix [RozElmRow, CurCol]/ SDMatrix [RozElmRow, RozElmCol]; {Заміняємо розв'язуючий елемент на обернене до нього число:} SDMatrix [RozElmRow, RozElmCol]:=1/SDMatrix [RozElmRow, RozElmCol]; {Міняємо місцями елементи рядка і стовпця-заголовків, що стоять у стовпці і рядку розв'язуючого елемента:} SafeHeadElm:= SDHeadRow[RozElmCol]; SDHeadRow[RozElmCol]:=SDHeadCol[RozElmRow]; SDHeadCol[RozElmRow]:=SafeHeadElm; {Якщо виконуємо модиівковане Жорданове виключення, то змінюють знаки і ці елементи, що помінялись місцями:} If ToDoMGI then Begin ChangeSignForValOrVarName (SDHeadRow[RozElmCol]); ChangeSignForValOrVarName (SDHeadCol[RozElmRow]); End; DColDeleted:=False; {Якщо у рядку-заголовку навпроти розв'язуючого елемента опинився нуль, і задано видаляти у такому випадку цей елемент разом із стовпцем розв'язуючого елемента у матриці, то видаляємо:} If ToDelColIfZeroInHRow and (SDHeadRow[RozElmCol].ElmType=bc_Number) then If SDHeadRow[RozElmCol].AsNumber=0 then Begin DeleteFromArr (SDHeadRow, RozElmCol, 1); DelColsFromMatr (SDMatrix, RozElmCol, 1); DColDeleted:=True; End; GI:=True; End; Procedure ChangeRowsPlaces (Var SDMatr:TFloatMatrix; Row1, Row2: Integer); overload; Var SafeCurRow:TFloatArr; Begin SafeCurRow:=SDMatr[Row1]; SDMatr[Row1]:=SDMatr[Row2]; SDMatr[Row2]:=SafeCurRow; End; Procedure ChangeRowsPlaces (Var SDMatr:TFloatMatrix; Var SDHeadCol:TValOrNameMas; Row1, Row2: Integer; ToChangeInitPosNums: Boolean=False); overload; {Процедура міняє місцями рядки у таблиці зі стовпцем-заголовком. Вхідні дані: SDMatr – таблиця; SDHeadCol – стовпець-заголовок таблиці; Row1, Row2 – рядки, що треба поміняти місцями; ToChangeInitPosNums – вмикач зміни номерів по порядку у стовпці-заголовку. Якщо рівний True, то рядки, що помінялися місцями, міняються також і позначками про номер по порядку та розміщення як рядка чи стовпця (що присвоювалися їм при створенні). Вихідні дані: SDMatr – таблиця; SDHeadCol – стовпець-заголовок таблиці.} Var SafeCurHeadCell:TValOrName; Begin SafeCurHeadCell:=SDHeadCol[Row1]; SDHeadCol[Row1]:=SDHeadCol[Row2]; SDHeadCol[Row2]:=SafeCurHeadCell; If ToChangeInitPosNums then Begin SDHeadCol[Row2].VarInitPos:=SDHeadCol[Row1].VarInitPos; SDHeadCol[Row2].VarInitInRow:=SDHeadCol[Row1].VarInitInRow; SDHeadCol[Row1].VarInitPos:=SafeCurHeadCell. VarInitPos; SDHeadCol[Row1].VarInitInRow:=SafeCurHeadCell. VarInitInRow; End; ChangeRowsPlaces (SDMatr, Row1, Row2); End; Procedure ChangePlaces (Var SDMas:TFloatArr; Elm1, Elm2: Integer); Var SafeElm:TWorkFloat; Begin SafeElm:=SDMas[Elm1]; SDMas[Elm1]:=SDMas[Elm2]; SDMas[Elm2]:=SafeElm; End; Procedure ChangeColsPlaces (Var SDMatr:TFloatMatrix; Col1, Col2: Integer); overload; Var CurRow: Integer; Begin For CurRow:=0 to Length(SDMatr) – 1 do ChangePlaces (SDMatr[CurRow], Col1, Col2); End; Procedure ChangeColsPlaces (Var SDMatr:TFloatMatrix; Var SDHeadRow:TValOrNameMas; Col1, Col2: Integer; ToChangeInitPosNums: Boolean=False); overload; {Процедура міняє місцями стовпці у таблиці з рядком-заголовком. Вхідні дані: SDMatr – таблиця; SDHeadRow – рядок-заголовок таблиці; Row1, Row2 – рядки, що треба поміняти місцями; ToChangeInitPosNums – вмикач зміни номерів по порядку у стовпці-заголовку. Якщо рівний True, то рядки, що помінялися місцями, міняються також і позначками про номер по порядку та розміщення як рядка чи стовпця (що присвоювалися їм при створенні). Вихідні дані: SDMatr – таблиця; SDHeadCol – рядок-заголовок таблиці.} Var SafeCurHeadCell:TValOrName; Begin SafeCurHeadCell:=SDHeadRow[Col1]; SDHeadRow[Col1]:=SDHeadRow[Col2]; SDHeadRow[Col2]:=SafeCurHeadCell; If ToChangeInitPosNums then Begin SDHeadRow[Col2].VarInitPos:=SDHeadRow[Col1].VarInitPos; SDHeadRow[Col2].VarInitInRow:=SDHeadRow[Col1].VarInitInRow; SDHeadRow[Col1].VarInitPos:=SafeCurHeadCell. VarInitPos; SDHeadRow[Col1].VarInitInRow:=SafeCurHeadCell. VarInitInRow; End; ChangeColsPlaces (SDMatr, Col1, Col2); End; Procedure TGridFormattingProcs. WaitForNewStep (HeadColNum, HeadRowNum: Integer); {Зупиняє хід вирішування, відображає поточний стан таблиці, і чекає, доки не буде встановлений один з прапорців: Self. Continue, Self. GoToEnd або Self. Stop. Якщо прапорці Self. GoToEnd або Self. Stop вже були встановлені до виклику цієї процедури, то процедура не чекає встановлення прапорців.} Begin {Якщо процедуру викликали, то треба почекати, доки не встановиться Self. Continue=True, незважаючи на поточний стан цього прапорця:} Self. Continue:=False; {Відображаємо поточний стан таблиці, якщо не ввімкнено режим роботи без зупинок:} If Not (Self. GoToEnd) then Self. WriteTableToGrid (HeadColNum, HeadRowNum, True); {Чекаємо підтвердження для наступного кроку, або переривання розв'язування:} While Not (Self. Continue or Self. GoToEnd or Self. Stop) do Application. ProcessMessages; End; Function TGridFormattingProcs. SearchNozeroSolveCell (CurRowNum, CurColNum, MaxRow, MaxCol: Integer; HeadRowNum, HeadColNum: Integer; ToSearchInRightColsToo: Boolean=True):Boolean; {Пошук ненульової розв'язувальної комірки для вирішування системи рівнянь або при вирішуванні задачі максимізації/мінімізації лінійної форми симплекс-методом (починаючи з комірки [CurRowNum, CurColNum]).} Const sc_CurProcName='SearchNozeroSolveCell'; Var CurSearchRowNum, CurSearchColNum: Integer; st1: String; Begin {Якщо комірка, що хотіли взяти розв'язувальною, рівна нулю:} If Self. CurTable [CurRowNum, CurColNum]=0 then Begin If Self. CurOutConsole<>Nil then Self. CurOutConsole. Lines. Add (sc_CurProcName+sc_ZeroKoef+ ' ['+IntToStr (CurColNum+1)+'; '+IntToStr (CurRowNum+1)+']'+ sc_SearchingOther); CurSearchRowNum:=MaxRow+1; {Шукаємо ненульову комірку в заданій області (або в одному її стовпці CurColNum, якщо ToSearchInRightColsToo=False):} For CurSearchColNum:=CurColNum to MaxCol do Begin {Шукаємо ненульову комірку знизу у тому ж стовпцю:} For CurSearchRowNum:=CurRowNum+1 to MaxRow do Begin If Self. CurTable [CurSearchRowNum, CurSearchColNum]<>0 then Break; End; {Якщо немає ненульових, то змінна вільна:} If CurSearchRowNum>MaxRow then Begin If Self. CurOutConsole<>Nil then Begin st1:=sc_CurProcName+sc_AllKoefIsZeroForVar; If Self. CurHeadRow[CurSearchColNum].ElmType=bc_Number then st1:=st1+sc_Space+ FloatToStr (Self. CurHeadRow[CurSearchColNum].AsNumber) Else st1:=st1+sc_Space+ sc_DoubleQuot+Self. CurHeadRow[CurSearchColNum].AsVarName+ sc_DoubleQuot; Self. CurOutConsole. Lines. Add(st1); End; {Якщо потрібна комірка тільки у даному стовпці (для даної змінної), то в інших стовцях не шукаємо:} If Not(ToSearchInRightColsToo) then Break; {For CurSearchColNum…} End Else {Якщо знайдено ненульовий:} Begin Self. WaitForNewStep (HeadColNum, HeadRowNum); {Якщо дано команду перервати розв'язування:} If Self. Stop then Begin SearchNozeroSolveCell:=True; Exit; End; {Ставимо рядок із знайденим ненульовим замість поточного:} ChangeRowsPlaces (Self. CurTable, Self. CurHeadCol, CurRowNum, CurSearchRowNum); {Якщо знайдена комірка у іншому стовпці, то міняємо місцями стовпці:} If CurColNum<>CurSearchColNum then ChangeColsPlaces (Self. CurTable, Self. CurHeadRow, CurColNum, CurSearchColNum); Break; {For CurSearchColNum:=CurColNum to MaxCol do…} End; End; {For CurSearchColNum:=CurColNum to MaxCol do…} {Якщо ненульову комірку не знайдено:} If (CurSearchColNum>MaxCol) or (CurSearchRowNum>MaxRow) then Begin If Self. CurOutConsole<>Nil then Self. CurOutConsole. Lines. Add (sc_CurProcName+sc_AllKoefIsZero); SearchNozeroSolveCell:=False; Exit; {задача не має розв'язків, або має їх безліч…} End; End; {If Self. CurTable [CurRowNum, CurColNum]=0 then…} SearchNozeroSolveCell:=True; End; {Вирішування системи лінійних рівнянь способом 1:} Function TGridFormattingProcs. SolveEqsWithM1: Boolean; {Для таблиці виду: x1 x2 x3… xn a1 a2 a3 … am} Const sc_CurProcName='SolveEqsWithM1'; Var CurRowNum, CurColNum: Integer; st1: String; HeadRowNum, HeadColNum: Integer; ColDeleted: Boolean; Procedure ShowResultCalc; {Відображає записи про обчислення значень змінних (у текстовому полі) такого зказка: <стовп1>=<a11>*<ряд1> + <a12>*<ряд2> +… + <a1n>*<рядn>; Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 |
|
|||||||||||||||||||||||||||||
|
Рефераты бесплатно, реферат бесплатно, курсовые работы, реферат, доклады, рефераты, рефераты скачать, рефераты на тему, сочинения, курсовые, дипломы, научные работы и многое другое. |
||
При использовании материалов - ссылка на сайт обязательна. |