11245. Разбиение на палиндромы

 

Разделение строки называется палиндромным разбиением, если каждая подстрока в этом разбиении является палиндромом. Например, строка “ababbbabbababa” может быть разделена как “aba|b|bbabb|a|b|aba” – это пример палиндромного разбиения.

Определите наименьшее количество разрезов, необходимых для палиндромного разбиения заданной строки. Например:

·        для строки “ababbbabbababa” требуется минимум 3 разреза, которые образуют разбиение “a|babbbab|b|ababa”;

·        если строка уже является палиндромом, то потребуется 0 разрезов;

·        если все символы строки длины n различны, то минимальное количество разрезов равно n – 1.

 

Вход. Одна строка s длины не более 1000 символов.

 

Выход. Выведите минимальное количество разрезов, необходимых для палиндромного разбиения строки s.

 

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

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

abbaazxzazbxbz

2

 

 

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

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

abccba

0

 

 

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

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

qwerty

5

 

 

РЕШЕНИЕ

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

 

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

Пусть s – входная строка. Подстрока si sj является палиндромом, если выполняется одно из следующих условий:

·        ij (подстрока пустая или состоит из одного символа);

·        si = sj и подстрока si+1sj-1 является палиндромом;

 

Пусть функция IsPal(i, j) возвращает 1, если sisj является палиндромом, и 0 в противном случае. Значения IsPal(i, j) сохраняем в ячейках pal[i][j].

Пусть функция f(i, j) возвращает минимальное количество разрезов, необходимых для палиндромного разбиения строки sisj. Значения этой функции будем сохранять в ячейках массива dp[i][j]. Тогда ответом на задачу будет значение f(0, s.size() – 1).

Отметим, что f(i, i) = 0, так как строка из одной буквы уже является палиндромом.

Для вычисления f(i, j) разрежем подстроку sisj на две части: sisk и sk+1sj (ik < j). Рекурсивно решаем задачи на строках sisk и sk+1sj. Ищем такое значение k (ik < j), для которого сумма f(i, k) + f(k + 1, j) + 1 будет минимальной. Слагаемое 1 в сумме означает, что мы совершаем один разрез между символами sk и sk+1. Таким образом, получаем следующее рекуррентное соотношение:

f(i, j) =

 

Пример

Например, для строки ababccbиз 7 символов имеем:

f(1, 7) =

Сумма принимает наименьшее значения при k = 3:

f(1, 7) = f(1, 3) + f(4, 7) + 1 = 0 + 0 + 1 = 1

Так как строкиaba иbccb являются палиндромами, то f(1, 3) = f(4, 7) = 0.

 

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

Объявим константы, входную строку s  и рабочие массивы.

 

#define MAX 1001

#define INF 0x3F3F3F3F

int dp[MAX][MAX], pal[MAX][MAX];

string s;

 

Функция IsPal(i, j) проверяет, является ли подстрока sisj палиндромом. Подстрока si sj является палиндромом, если si = sj и si+1sj-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) возвращает минимальное количество разрезов, необходимых для палиндромного разбиения подстроки sisj.

 

int f(int i, int j)

{

 

Если i = j, подстрока sisj состоит из одного символа, и резать её нет смысла.

Если i > j, подстрока sisj считается пустой, и разрезы также не требуются.

 

  if (i >= j) return 0;

 

Если значение f(i, j) уже было вычислено ранее, возвращаем сохранённое значение из массива dp[i][j].

 

  if (dp[i][j] != INF) return dp[i][j];

 

Если подстрока sisj (i < j) является палиндромом, то резать ее смысла нет. В этом случае минимальное количество разрезов равно 0.

 

  if (IsPal(i, j)) return dp[i][j] = 0;

 

Разрежем подстроку sisj на две части: sisk (ik) и sk+1sj (k + 1 j). Далее рекурсивно решаем задачи для обеих частей: sisk и sk+1sj. Ищем такое значение k (ik < 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;