Нехай — вхідний рядок. Підрядок s_i~...~s_j є паліндромом, якщо виконується одна з наступних умов:
i \ge j (підрядок порожній або складається з одного символу);
s_i = s_j і підрядок s_{i+1}~...~s_{j-1} є паліндромом;
Нехай функція IsPal(i, j) повертає , якщо s_i ... s_j є паліндромом, і в іншому випадку. Значення IsPal(i, j) зберігаємо в комірках pal[i][j].
Нехай функція f(i, j) повертає мінімальну кількість розрізів, необхідних для паліндромного розбиття рядка s_i~...~s_j. Значення цієї функції будемо зберігати в комірках масиву dp[i][j]. Тоді відповіддю на задачу буде значення f(0, s.size() - 1).
Зазначимо, що f(i, i) = 0, тому що рядок з однієї букви вже є паліндромом.
Для обчислення f(i, j) розділимо підрядок s_i~...~s_j на дві частини: s_i~...~s_k і s_{k+1}~...~s_j~(i \le k < j). Рекурсивно розв'язуємо задачі на рядках s_i~...~s_k і s_{k+1}~...~s_j. Шукаємо таке значення k~(i \le k < j), для якого сума f(i, k) + f(k + 1, j) + 1 буде мінімальною. Доданок в сумі означає, що ми робимо один розріз між символами s_k та s_{k+1}. Таким чином, отримуємо наступне рекурентне співвідношення:
Приклад
Наприклад, для рядка "" з символів маємо:
Сума набуває найменшого значення при k = 3:
Оскільки рядки "" і "" є паліндромами, то f(1, 3) = f(4, 7) = 0.
Реалізація алгоритму
Оголосимо константи, вхідний рядок та робочі масиви.
#define MAX 1001
#define INF 0x3F3F3F3F
int dp[MAX][MAX], pal[MAX][MAX];
string s;Функція IsPal(i, j) перевіряє, чи є підрядок s_i~...~s_j паліндромом. Підрядок s_i~...~s_j є паліндромом, якщо s_i = s_j і s_{i+1}~...~s_{j-1} є паліндромом. Значення IsPal(i, j) зберігаємо в комірках масиву pal[i][j].
int IsPal(int i, int j)
{
if (i >= j) return pal[i][j] = 1;
if (pal[i][j] != -1) return pal[i][j];
return pal[i][j] = (s[i] == s[j]) && IsPal(i + 1, j - 1);
}Функція f(i, j) повертає мінімальну кількість розрізів, необхідних для паліндромного розбиття підрядка s_i~...~s_j.
int f(int i, int j)
{Якщо i = j, підрядок s_i~...~s_j складається з одного символу, і розрізати її немає сенсу. Якщо i > j, підрядок s_i~...~s_j вважається порожнім, і розрізи також не потрібні.
if (i >= j) return 0;Якщо значення f(i, j) вже було обчислено раніше, повертаємо збережене значення з масиву dp[i][j].
if (dp[i][j] != INF) return dp[i][j];Якщо підрядок s_i~...~s_j~(i < j) є паліндромом, то розрізати її немає сенсу. В цьому випадку мінімальна кількість розрізів дорівнює .
if (IsPal(i, j)) return dp[i][j] = 0;Розділимо підрядок s_i~...~s_j на дві частини: s_i~...~s_k~(i \le k) і s_{k+1}~...~s_j~(k + 1 \le j). Далі рекурсивно розв'язуємо задачі для обох частин: s_i~...~s_k і s_{k+1}~...~s_j. Шукаємо таке значення k~(i \le k < j), для якого сума f(i, k) + f(k + 1, j) + 1 найменша.
for (int k = i; k < j; k++)
dp[i][j] = min(dp[i][j], f(i, k) + f(k + 1, j) + 1);Повертаємо відповідь dp[i][j] = f(i, j).
return dp[i][j];
}Основна частина програми. Читаємо вхідний рядок.
cin >> s;Ініціалізуємо масиви.
memset(dp, 0x3F, sizeof(dp));
memset(pal, -1, sizeof(pal));Обчислюємо і виводимо відповідь.
res = f(0, s.size() - 1);
cout << res << endl;