Moortal Kombat#10292

Moortal Kombat (Розбір)

Поступимо за алгоритмом Флойда-Воршелла на матриці a_{ij}, щоб визначити найкоротший час заміни кожної букви на будь-яку іншу.

Складемо матрицю витрат таку, що cst[i][j]~(1 \le i \le n) містить найменшу кількість днів, за яку можна змінити букву s[i - 1] на букву .

Для зручності подальших обчислень побудуємо матрицю , що містить значення часткових сум матриці витрат за стовпцями:

ps[i][j] = cst[i][1] + cst[i][2] + ... + cst[i][j]

Нехай dp[i][j] містить найменшу кількість днів, за яку можна перетворити перші букв рядка в рядок, що задовольняє новим вимогам Бессі, причому останні букви в новому рядку будуть буквою .

Запишемо рівняння динаміки:

cst[i - k + 1][j] + cst[i - k + 2][j] + ... + cst[i][j]

днів. Зазначену суму можна обчислити за константний час, використовуючи масив префіксів :

cst[i - k + 1][j] + cst[i - k + 2][j] + ... + cst[i][j] = ps[i][j] - ps[i - k][j]

Підтримуватимемо додатковий масив , де

mn[i] = min(dp[i][0], dp[i][1], ..., dp[i][m - 1])

Таким чином, рівняння динаміки для цього випадку (i \ge k) матиме вигляд:

dp[i][j] = ps[i][j] - ps[i - k][j] + mn[i - k]

Залишається серед двох варіантів для dp[i][j] вибрати найменший.

Відповідь на задачу буде значення

mn[n] = min(dp[n][0], dp[n][1], ..., dp[n][m - 1])

Нам не важливо, на які саме букви закінчується нове слово Бессі.

Приклад

У даному прикладі оптимальне рішення полягає в наступному: замінити на , потім на , а після цього обидві — на . Загальна вартість змін складе 1 + 4 + 0 + 0 = 5 днів, а підсумковий рядок матиме вигляд .

За матрицею a_{ij} побудуємо матрицю витрат . Обчислимо також матрицю .

Усі значення dp[1][j]~(0 \le j \le 4) будуть рівні , оскільки довжина смуги не може бути меншою k = 2. Відповідно, mn[1] = +∞.

Розглянемо процес обчислення значень dp[2][j] (0 ≤ j ≤ 4).

mn[2] = min(dp[2][0], dp[2][1], dp[2][2], dp[2][3] , dp[2][4]) = 1

При обчисленні значень dp[3][j]~(0 \le j \le 4) рівняння

dp[3][j] = ps[3][j] - ps[1][j] + mn[1]

не використано, оскільки mn[1] = +∞. Тому для отримання смуг з перших букв можна лише продовжити вже побудовані смуги довжини :

Значення можна отримати двома способами:

або

Другий випадок оптимальніший, додаються дві букви '' до рядка з двох символів. Значення mn[2] = 1 досягається на рядку "", а значить оптимальною строкою буде "".

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

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

#define MAXN 100005
#define ALPH 26
int a[ALPH][ALPH], cst[MAXN][ALPH], ps[MAXN][ALPH], dp[MAXN][ALPH], mn[MAXN];

Читаємо вхідні дані.

cin >> n >> m >> k;
cin >> s;
for (i = 0; i < m; i++)
for (j = 0; j < m; j++)
   cin >> a[i][j];

Запускаємо алгоритм Флойда-Воршелла на матриці . Після його завершення містить найменшу кількість днів, за яку можна змінити букву на букву .

for (x = 0; x < m; x++)
for (i = 0; i < m; i++)
for (j = 0; j < m; j++)
  a[i][j] = min(a[i][j], a[i][x] + a[x][j]);

У нас є m \le 26 букв. Складемо матрицю витрат таку, що cst[i][j]~(1 \le i \le n) містить найменшу кількість днів, за яку можна змінити букву s[i - 1] на букву . Індексація букв у рядку починається з .

Для зручності подальших обчислень побудуємо матрицю , що містить значення часткових сум матриці витрат за стовпцями:

ps[i][j] = cst[i][1] + cst[i][2] + ... + cst[i][j]
for (i = 1; i <= n; i++)
for (j = 0; j < m; j++)
{
  cst[i][j] = a[s[i - 1] - 'a'][j];
  ps[i][j] = ps[i - 1][j] + cst[i][j];
}

Ініціалізуємо масиви.

memset(dp, 0x3f, sizeof dp);
memset(mn, 0x3f, sizeof mn);
mn[0] = 0;

Вичислюємо значення ячеек масиву . Букві відповідає символ s[i - 1]. Нумерація букв у змінній йде від (якій відповідає буква '') до m - 1. Інтуїтивно на кожному кроці Бессі вирішує:

Ми обираємо те, що дешевше, і записуємо результат в dp[i][j].

Цикл по починаємо з , тому що перша допустима смуга може закінчитися лише на позиції .

for (i = k; i <= n; i++)
for (j = 0; j < m; j++)
{

Продовжуємо поточну смугу. Букву початкового рядка s[i - 1] змінюємо на . Тобто:

  dp[i][j] = min(dp[i][j], dp[i - 1][j] + cst[i][j]);

Починаємо нову смугу з букв. Вважаємо, що на позиції закінчується нова смуга довжиною рівно (або більше), що складається з букви . Для цього:

ps[i][j] - ps[i - k][j]
  dp[i][j] = min(dp[i][j], ps[i][j] - ps[i - k][j] + mn[i - k]);

Після обчислення всіх dp[i][j] для фіксованого , зберігаємо найкраще (мінімальне) значення серед усіх в .

  mn[i] = min(mn[i], dp[i][j]);
}

Виводимо відповідь.

cout << mn[n] << "\n";