Галька#10837

Галька (Розбір)

Оскільки символ зустрічається не менше ніж 2 \cdot k - 1 разів, то гра обов'язково закінчиться перемогою. Гра не закінчиться перемогою, якщо кожен з учасників матиме не більше k - 1 червоних каменів. Тобто всього в грі повинно бути не більше 2k - 2 червоних каменів, що суперечить умові.

Побудуємо масив prefix щоб швидко знаходити кількість червоних камінців на будь-якому підвідрізку [l, r]:

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

Тоді число червоних камінців на підвідрізку [l, r] можна обчислити за :

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

Розглянемо наступну функцію:

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

Умови закінчення гри:

Гравці по черзі здійснюють ходи:

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;

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

Виклик функції і виведення результатів:

if (can_win(0, n - 1, 0))
  puts("DA");  // Антун може гарантувати перемогу
else
  puts("NE");  // ні

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

Оголосимо робочі масиви:

#define MAX 352
int prefix[MAX], dp[MAX][MAX][MAX];

Функція can_win повертає , якщо гравець (Антун або Бранка) може гарантувати перемогу в цій позиції, інакше .

int can_win(int l, int r, int uk)
{
  int& res = dp[l][r][uk];

Якщо значення функції ще не обчислено.

  if (res == -1)
  {

Обчислюємо скільки червоних каменів red\_left лежить у інтервалі [l, r];

Обчислюємо скільки червоних каменів total\_red - red\_left - 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 \ge k, то Антун / Бранка вже набрав червоних каменів, значить він / вона програв.

    if (uk >= k)
      res = 0;

Якщо у Бранки / Антуна обов'язково вийде зібрати червоних каменів, то Антун / Бранка виграє.

    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[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;