Редакция для Игра «Виселица»
Submitting an official solution before solving the problem yourself is a bannable offence.
Автор:
1. Идея
Нужно аккуратно смоделировать игру «Виселица» ровно по правилам из условия.
Есть:
- загаданное слово
word, - число жизней
lives, - последовательность ходов
moves.
На каждом ходе надо понять, что произошло:
- букву уже называли раньше →
REPEAT, - буква есть в слове →
HIT, - буквы нет в слове →
MISS, и уменьшаемlives.
После каждого обработанного хода игра может завершиться:
- если открыто всё слово, то
WIN, - если жизни закончились, то
LOSE.
Если игра завершилась, остальные ходы больше не обрабатываются.
2. Наблюдения
Наблюдение 1. Нужно помнить, какие буквы уже назывались
Так как алфавит состоит только из строчных латинских букв, удобно хранить массив из 26 значений used.
used[0]соответствует букве'a',used[1]—'b',- ...
used[25]—'z'.
Это позволяет за O(1) проверять, была ли буква названа раньше.
Наблюдение 2. Состояние слова можно хранить отдельно
Текущее состояние удобно хранить в строке или списке символов длины |word|:
- в начале там только
'_', - при удачном ходе открываем все позиции, где стоит названная буква.
Например, если word = "apple", то состояние может меняться так:
"_____""a____""app__""apple"
Наблюдение 3. Один ход может открыть сразу несколько позиций
Если буква встречается в слове несколько раз, нужно открыть все её вхождения за один ход.
Поэтому при новом символе c надо пройтись по всему слову и заменить все подходящие '_' на c.
Наблюдение 4. После завершения партии дальнейшие ходы игнорируются
Это важный момент условия. Если уже получен WIN или LOSE, то:
- не печатаем результаты для оставшихся ходов,
- не меняем состояние,
- просто выводим финальное состояние и итог.
3. Алгоритм
- Считываем
word,lives,q,moves. - Создаём:
- массив
usedиз26значенийfalse, - текущее состояние
state, заполненное символами'_'.
- массив
- Заводим флаг
finished = false. - Для каждого хода
moves[i]:- если
finished == true, прекращаем обработку; - находим номер буквы:
id = c - 'a'; - если
used[id]уже истинно:- выводим
REPEAT;
- выводим
- иначе:
- помечаем букву как использованную;
- ищем эту букву во всём слове;
- если нашли хотя бы одно вхождение:
- открываем все такие позиции;
- выводим
HIT;
- иначе:
- уменьшаем
livesна1; - выводим
MISS.
- уменьшаем
- после этого проверяем:
- если
state == word, то партия завершена; - если
lives == 0, то партия завершена.
- если
- если
- После цикла выводим финальное состояние слова.
- Определяем итог:
- если
state == word, выводимWIN; - иначе если
lives == 0, выводимLOSE; - иначе выводим
PLAY.
- если
4. Почему это работает
Докажем, что алгоритм точно соответствует правилам задачи.
1. Повторные ходы обрабатываются правильно
Для каждой буквы мы запоминаем, называлась ли она раньше, в массиве used.
- Если буква уже была, алгоритм выводит
REPEAT. - При этом состояние слова не меняется.
- Число жизней тоже не меняется.
Это в точности совпадает с условием.
2. Новая буква из слова обрабатывается правильно
Если буква ещё не использовалась, мы помечаем её в used и просматриваем всё слово.
- Все позиции, где
word[j] == c, открываются вstate. - Если хотя бы одна такая позиция есть, выводим
HIT. - Жизни при этом не уменьшаются.
Это ровно соответствует правилу для удачного угадывания.
3. Новая буква, которой нет в слове, обрабатывается правильно
Если при просмотре слова не найдено ни одной позиции с буквой c, то:
- выводим
MISS, - уменьшаем
livesна1.
Это в точности правило промаха.
4. Завершение игры определяется правильно
После каждого обработанного хода алгоритм проверяет два условия завершения:
state == word— значит, все буквы открыты, нужно завершить игру с результатомWIN;lives == 0— значит, попытки закончились, игра завершается с результатомLOSE.
Если хотя бы одно из этих условий выполнилось, мы прекращаем дальнейшую обработку ходов. Это соответствует фразе из условия, что партия завершается сразу, а оставшиеся ходы игнорируются.
5. Финальный ответ корректен
После завершения обработки:
stateсодержит состояние слова после последнего реально обработанного хода;- если слово полностью открыто, выводим
WIN; - если жизни закончились, выводим
LOSE; - иначе ходы просто закончились раньше завершения партии, значит выводим
PLAY.
Следовательно, алгоритм полностью моделирует игру по условию.
5. Сложность
Пусть:
n = |word|,q— число ходов.
Для каждого хода мы в худшем случае один раз просматриваем всё слово, то есть тратим O(n).
Итого:
- время:
O(q * n), - память:
O(n + 26).
С учётом ограничений n <= 50 и q <= 100 этого более чем достаточно.
6. Код на C++17
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
string word;
cin >> word;
int lives, q;
cin >> lives;
cin >> q;
string moves;
cin >> moves;
vector<bool> used(26, false);
string state(word.size(), '_');
bool finished = false;
for (int i = 0; i < q; i++) {
if (finished) {
break;
}
char c = moves[i];
int id = c - 'a';
if (used[id]) {
cout << "REPEAT\n";
} else {
used[id] = true;
bool hit = false;
for (int j = 0; j < (int)word.size(); j++) {
if (word[j] == c) {
state[j] = c;
hit = true;
}
}
if (hit) {
cout << "HIT\n";
} else {
lives--;
cout << "MISS\n";
}
}
if (state == word || lives == 0) {
finished = true;
}
}
cout << state << "\n";
if (state == word) {
cout << "WIN\n";
} else if (lives == 0) {
cout << "LOSE\n";
} else {
cout << "PLAY\n";
}
return 0;
}
7. Код на Python 3
word = input().strip()
lives = int(input())
q = int(input())
moves = input().strip()
used = [False] * 26
state = ['_'] * len(word)
finished = False
for i in range(q):
if finished:
break
c = moves[i]
idx = ord(c) - ord('a')
if used[idx]:
print("REPEAT")
else:
used[idx] = True
hit = False
for j in range(len(word)):
if word[j] == c:
state[j] = c
hit = True
if hit:
print("HIT")
else:
lives -= 1
print("MISS")
if ''.join(state) == word or lives == 0:
finished = True
final_state = ''.join(state)
print(final_state)
if final_state == word:
print("WIN")
elif lives == 0:
print("LOSE")
else:
print("PLAY")
Комментарии