10837. Галька

 

Этим летом Антун и Бранка наткнулись на необычный пляж, полностью покрытый пластиковыми камешками, выброшенными морем из контейнеров, упавших с грузовых судов. Они решили забрать с собой n таких камешков: красных и синих.

Осенью, вспоминая тёплые дни, Антун и Бранка придумали игру с этими камешками. Сначала они выкладывают все n камешков в ряд. Затем игроки делают ходы по очереди: каждый раз необходимо убрать один камешек с одного из концов ряда. Проигрывает тот, кто первым наберёт k красных камешков. Антун всегда делает первый ход и задаётся вопросом: сможет ли он гарантировать победу, независимо от действий Бранки? Ваша задачапомочь ему ответить на этот вопрос.

 

Вход. Первая строка содержит два целых числа n и k (1 k < n 350).

Вторая строка содержит последовательность из n символов C или P, где C обозначает красный камешек, а P синий. Гарантируется, что символ C встречается не менее чем 2 * k 1 раз.

 

Выход. Выведите DA, если Антун может выиграть независимо от ходов Бранки, и NE в противном случае.

 

Пример входа 1

Пример выхода 1

4 1

CCCP

DA

 

 

Пример входа 2

Пример выхода 2

8 2

PCPPCCCC

DA

 

 

Пример входа 3

Пример выхода 3

9 1

PPCPPCPPC

NE

 

 

РЕШЕНИЕ

динамическое программирование

 

Анализ алгоритма

Поскольку символ C встречается не менее чем 2 * k 1 раз, то игра обязательно закончится победой. Игра не закончится победой, если у каждого из участников будет не более k – 1 красных камней. То есть всего в игре должно быть не более 2k – 2 красных камней, что противоречит условию.

Построим массив prefix чтобы быстро находить количество красных камешков на любом подотрезке [l, r]:

prefix[0] = (str[0] == 'C')

prefix[i] = prefix[i-1] + (str[i] == 'C')

Тогда число красных камешков на подотрезке [l, r] можно вычислить за O(1):

red_left = prefix[r];

if (l) red_left -= prefix[l-1];

 

Рассмотрим следующую функцию:

int can_win(int l, int r, int uk)

·        Сейчас ходит Антун (первый игрок).

·        Подотрезок камней: с l по r.

·        У Антуна уже имеется uk красных камней.

·        Функция возвращает 1, если Антун может гарантировать победу в этой позиции, иначе 0.

 

Условия окончания игры:

·        Если uk >= k, то Антун уже набрал k, значит он проиграл.

·        Если у Бранки обязательно получится собрать k красных камней, то Антун выигрывает.

 

Игроки по очереди совершают ходы:

·        Антун может взять камень слева (l + 1) или справа (r – 1).

·        Чтобы Антун гарантировал себе победу, хотя бы один из его ходов должен привести к позиции, где у Бранки нет выигрышной стратегии.

 

if (!can_win(l + 1, r, other_red) || !can_win(l, r - 1, other_red))

  res = 1;

else

  res = 0;

 

Здесь other_red – количество красных камней, которые накопятся у Бранки. То есть мы моделируем ситуацию: я хожу, потом ходит соперник. Если имеется хотя бы один ход, после которого соперник гарантированно не выигрывает, то Антун выигрывает.

Значение other_red вычисляется следующим образом:

 

int total_red = prefix[n - 1]; // всего красных в строке

int red_left = prefix[r];    // красных до позиции r

if (l) red_left -= prefix[l - 1]; // красных на [l, r]

int other_red = total_red - red_left - uk;

 

·        red_left – сколько красных камней лежит в текущем интервале [l, r];

·        total_redred_leftuk = сколько красных камней останется сопернику (Бранке), если Антун уже набрал uk.

 

Таким образом, игра сведена к рекурсивному DP по трём параметрам:

·        (l, r, uk) подотрезок и число красных камней у Антуна.

Вызов функции и вывод результатов:

if (can_win(0, n - 1, 0))

  puts("DA");  // Антун может гарантировать победу

else

  puts("NE");  // нет

 

Реализация алгоритма

Объявим рабочие массивы:

·        prefix префиксные суммы красных камешков.

·        dp мемоизация для динамического программирования игры.

 

#define MAX 352

int prefix[MAX], dp[MAX][MAX][MAX];

 

Функция can_win возвращает 1, если игрок (Антун или Бранка) может гарантировать победу в этой позиции, иначе 0.

·        Сейчас ходит Антун / Бранка.

·        Подотрезок камней: с l по r.

·        У Антуна / Бранки уже имеется uk красных камней.

 

int can_win(int l, int r, int uk)

{

  int& res = dp[l][r][uk];

 

Если значение функции еще не вычислено.

 

  if (res == -1)

  {

 

Вычисляем сколько красных камней red_left лежит в интервале [l, r];

Вычисляем сколько красных камней total_redred_leftuk останется сопернику (Бранке / Антуну), если Антун / Бранка уже набрал uk.

 

    int total_red = prefix[n - 1];

    int red_left = prefix[r];

    if (l) red_left -= prefix[l - 1];

    int other_red = total_red - red_left - uk;

 

Если uk >= k, то Антун / Бранка уже набрал k красных камней, значит он проиграл.

 

    if (uk >= k)

      res = 0;

 

Если у Бранки / Антуна обязательно получится собрать k красных камней, то Антун / Бранка выигрывает.

 

    else if (other_red >= k)

      res = 1;

    else

    {

 

Антун / Бранка может взять камень слева (l + 1) или справа (r – 1). Чтобы Антун гарантировал себе победу, хотя бы один из его ходов должен привести к позиции, где у Бранки / Антуна нет выигрышной стратегии.

 

      if (!can_win(l + 1, r, other_red) ||

          !can_win(l, r - 1, other_red))

        res = 1;

      else

        res = 0;

    }

  }

  return res;

}

 

Основная часть программы. Читаем входные данные.

 

memset(dp, -1, sizeof(dp));

cin >> n >> k;

cin >> str;

 

Вычисляем массив префиксов prefix.

·        prefix[i] содержит количество красных камней в позициях от 0 до i включительно.

 

prefix[0] = (str[0] == 'C');

for (i = 1; i < n; i++) prefix[i] = prefix[i - 1] + (str[i] == 'C');

 

Вызываем функцию и выводим результат:

 

if (can_win(0, n - 1, 0))

  cout << "DA" << endl;

else

  cout << "NE" << endl;