|
Лекции по структурам и алгоритмам обработки данных (СиАОД) 2114.4. Дерево игры (5) Игра для двух игроков определяется ее деревом. Соперники делают ходы по очереди. Первый игрок начинает игру. Игра кончается или вничью, или победой одного из игроков. Листья дерева этой игры могут иметь значения, равные одному из трёх чисел: +1 - победа первого игрока, -1 - победа второго игрока, 0 - ничья. Ваша задача - определить, кто выиграет, если оба противника следуют правильной стратегии. Ввод из файла INPUT.TXT. Узлы дерева пронумерованы последовательными целыми числами. Корень дерева всегда имеет номер 1. Первая строка входного файла содержит целое N - число узлов в дереве игры. Следующая N - 1 строка описывает узлы - одна строка для каждого узла (за исключением первого). Вторая строка содержит описание второго узла дерева, третья - третьего узла и т.д. Если узел является листом, первый символ строки - L, затем идёт пробел, затем номер родительского узла, ещё пробел и результат игры (+1 - победа первого игрока, -1 - победа второго, 0 - ничья). Если узел внутренний, то строка содержит N - первый символ, затем пробел и номер родительского узла. Вывод в файл OUTPUT.TXT. Выводится +1, если выигрывает первый игрок, -1, если второй, и 0 - в случае ничейного исхода. Ограничения: 2 N 1000, время 1 с. Примеры Ввод 1 Ввод 2 7 7 N 1 N 1 N 1 N 1 L 2 -1 L 2 -1 L 2 +1 L 2 +1 L 3 +1 L 3 +1 L 3 +1 L 3 0 Вывод 1 Вывод 2 +1 0
18 N 1 N 1 N 2 L 2 +1 N 3 L 3 +1 L 3 +1 L 4 -1 L 4 +1 N 4 N 6 L 6 -1 L 6 -1 L 11 -1 L 11 +1 L 12 +1 L 12 -1 Вывод 3 +1
Однокопеечные монетки разложены в стопки (в стопках может быть различное количество монет), а стопки поставлены на столе в ряд слева направо. Двое противников по очереди делают ходы. Ход состоит в том, что один из игроков берет слева несколько стопок подряд, не меньше одной, но и не больше, чем перед этим взял его соперник. Первый игрок своим первым ходом берет не более K стопок. Игра заканчивается, когда стопок не остается. Требуется найти максимальное число монет, которое может получить первый участник после окончания игры, если второй игрок тоже старается ходить так, чтобы получить как можно больше монет. Ввод из файла INPUT.TXT. В первой строке находятся числа стопок N и K через пробел. Во второй строке идут через пробел N чисел, задающих количество монет в стопках слева направо. Ограничения: 1 N 180, 1 K 80, количество монет в стопке - не менее 1 и не более 20 000, время 1 с. Вывод в файл OUTPUT.TXT. Вывести одно число - максимальное количество монет, которое заведомо может получить первый игрок, как бы ни играл второй. Примеры Ввод 1 Ввод 2 Ввод 3 3 3 4 3 5 2 4 9 1 1 2 2 7 3 4 8 1 7 Вывод 1 Вывод 2 Вывод 3 14 5 18
14.6. Касса (5) Написать программу для игры касса, рассмотренной в настоящем разделе. Ввод из файла INPUT.TXT. В единственной строке находится количество монет N (N 200). Вывод в файл OUTPUT.TXT. Вывести 1, если при правильной игре выигрывает первый игрок, либо 2 – если выигрывает второй. Примеры Ввод 1 Ввод 2 3 2 Вывод 1 Вывод 2 2 1
14.7. Слитки (6) Написать программу для игры слитки, рассмотренной в настоящем разделе. Ввод из файла INPUT.TXT. В первой строке находится количество слитков N. Во второй строке идут через пробел N чисел (N 200), задающих стоимости слитков в порядке слева направо. Вывод в файл OUTPUT.TXT. Вывести через пробел два числа: суммы стоимостей слитков, которые может гарантировать каждый игрок при правильной игре. Пример Ввод 4 1 2 4 8 Вывод 9 6 15. Дерево отрезков Дерево отрезков - структура данных, которая позволяет реализовать с трудоемкостью O(log N) операции следующего типа:
Обычно обеспечиваются совместно первая и третья либо вторая и третья функции. Объём дополнительно используемой памяти составляет O(N), или, если быть точным, не более 8N на каждую пару функций. Распространенная аббревиатура Rsq расшифровывается как Range Sum Query, а Rmq как Range Maximum Query. Обычный массив позволяет выполнить с минимальной трудоемкостью O(1) операцию изменения одного элемента, но указанные операции имеют трудоемкость O(N). Если они должны выполняться часто, то при большой размерности массива это оказывается недопустимым. Рассмотрим сначала дерево отрезков для суммы. Пусть имеется массив A[1], A[2],…, A[N]. Построим бинарное дерево T следующим образом. Корень дерева будет храниться в элементе T[1] и содержать сумму элементов всего массива. Левый сын корня будет храниться в элементе T[2] и содержать сумму первой половины массива: A[1..N div 2], а правый сын - в элементе T[3] и содержать сумму элементов A[N div 2+1..N]. В общем случае, если i-й элемент содержит сумму элементов с L-го по R-й, то его левым сыном будет элемент T[2*i] с суммой элементов A[L..(L+R) div 2], а правым – элемент T[2*i +1] с суммой A[(L+R) div 2+1..R]. Исключение, разумеется, составляют листья дерева - вершины, в которых L = R. Дерево состоит из 2N-1 вершин, строится с трудоемкостью O(N) и имеет высоту порядка O(log N). Рассмотрим теперь операцию суммы на некотором отрезке индексов [L; R]. Мы встаем в корень дерева (i=1), и рекурсивно движемся вниз по этому дереву. Если в какой-то момент оказывается, что L и R совпадают с границами отрезка текущего элемента, то мы просто возвращаем значение текущего элемента T. Если же отрезок [L; R] целиком попадает в отрезок левого или правого сына текущего элемента, то мы рекурсивно вызываем себя из этого сына и найденное значение возвращаем. Наконец, если отрезок [L; R] частично принадлежит и отрезку левого сына, и отрезку правого сына, то делим его на два отрезка [L; M] и [M+1; R], где M = (L+R) div 2, и рекурсивно вызываем себя и для первого, и для второго отрезков, возвращая сумму найденных значений. В итоге вся операция суммирования работает за O (log N). Теперь рассмотрим простейший вариант операции присвоения значения некоторому единственному элементу с индексом K. Будем спускаться по дереву от корня, ища тот лист, который содержит значение элемента A[K]. Когда мы найдём этот элемент, просто изменим соответствующее значение в массиве T и будем подниматься от текущего элемента обратно к корню, пересчитывая текущие значения T. Понятно, что таким образом мы изменим все значения в дереве, которые необходимо изменить. Общая трудоемкость составляет O(log N). Приведем реализацию описанных операций.
Операция прибавления на отрезке реализуется сложнее одиночной модификации. Введем дополнительный массив Add размерности 2N – 1 и той же адресации, что и массив T. В i-м элементе будем хранить Add[i] - значение, которое нужно прибавить ко всем элементам этого отрезка. Если добавка распространяется не на все элементы, то это будет отражено в соответствующих вершинах поддерева с корнем I. Операция прибавления на отрезке [L; R] будет модифицировать эти значения, а операция суммирования на отрезке - просто добавлять к ответу все встретившиеся значения Add на пути от корня до вершин, которые представляют в совокупности отрезок [L; R]. Рекурсивная процедура Plus прибавления на отрезке имеет аргументы L, R, X, I, Tl, Tr. Здесь L и R описывают диапазон индексов исходного массива A, на котором к каждому элементу прибавляется значение X. Параметр I определяет индекс вершины в дереве T, содержащей диапазон индексов [Tl; Tr] исходного массива для поиска всех вершин дерева, которые описывают совместно отрезок [L; R]. Первоначально I = 1, что соответствует корню дерева. Значения Tl = 1 и Tr = N задают полный диапазон индексов исходного массива A. Значение X может быть как положительным, так и отрицательным. Порядок рекурсивных вызовов принципиально не меняется, что видно из текста процедуры Plus.
Рекурсивная функция суммирования имеет те же аргументы и строится по тому же принципу. Поскольку массив Add описывает изменение каждого элемента в диапазоне индексов [L; R], то общая добавка составляет сумму Add[i]*(R-L+1) по всем путям от корня дерева к вершинам, представляющим совместно весь диапазон [L; R].
Трудоемкость обеих рассмотренных операций по-прежнему составляет O(log N). Операция присвоения нового значения на диапазоне индексов реализуется с помощью дополнительного массива Val размерности 2N – 1 и той же адресации, что и массив T. Изначально массив Val заполняется некоторыми значениями, имеющими смысл ”неопределенность”. Если все элементы в текущем отрезке индексов массива T равны между собой, то соответствующий элемент массива Val примет это значение. При выполнении операции присвоения мы будем спускаться по дереву, как и ранее. Если в какой-то момент диапазон присваивания совпадет с границами текущего отрезка, то мы присвоим соответствующему элементу Val новое значение. При спуске необходимо отменять ранее определенные значения Val. Операция суммирования тоже должна быть изменена. Если при спуске в какой-то момент встречается элемент Val, отличный от неопределенного, то спуск прекращается. Действительно, результат уже определен значением элемента Val. При продолжении спуска были бы получены неправильные, старые значения. Приведем тексты программ для реализации описанных операций.
Procedure Update(L,R,NewVal,i,Tl,Tr:longint); {в интервале индексов [L,R] в A[i] новое значение NewVal, i-корень дерева отрезков} Var k,m: longint; Begin if (L=Tl) and (R=Tr) then begin Val[i]:=NewVal; T[i]:=NewVal; end else begin k:=Val[i]; Val[i]:=-1; if k<>-1 then begin Val[2*i]:=k; {спуск на уровень} Val[2*i+1]:=k; end; m:=(Tl+Tr) div 2; if R<=m then Update(L,R,NewVal,2*i,Tl,m) else if L>m then Update(L,R,NewVal,2*i+1,m+1,Tr) else begin Update(L,m,NewVal,2*i,Tl,m); Update(m+1,R,NewVal,2*i+1,m+1,Tr); end; T[i]:=T[2*i]+T[2*i+1]; {пересчет на обратном пути при выходе из рекурсии} end; End;
Function Sum(L,R,i,Tl,Tr:longint):longint; {L,R - интервал индексов для суммы, i-корень дерева, Tl,Tr - границы поиска в дереве} Var m: longint; Begin if (Val[i]<>-1) then Sum:=Val[i]*(R-L+1) {все вершины дерева с корнем i имеют значение Val[i]} else if Tl=Tr then Sum:=T[i] {дошли до листа без модификации значений} else begin m:=(Tl+Tr) div 2; if R<=m then Sum:=Sum(L,R,2*i,Tl,m) else if L>m then Sum:=Sum(L,R,2*i+1,m+1,Tr) else Sum:=Sum(L,m,2*i,Tl,m)+Sum(m+1,R,2*i+1,m+1,Tr); end; End; |
Loading
|