Перейти до основного вмісту

Динамічне програмування за зламаним профілем. Задача «Паркет»

До типових задач, які розв'язують за допомогою ДП за зламаним профілем, належать:

  • знаходження кількості способів повністю заповнити певну область (наприклад, шахову дошку чи ґратку) деякими фігурами (наприклад, доміно)
  • знаходження способу заповнити область мінімальною кількістю фігур
  • знаходження часткового заповнення з мінімальною кількістю незаповненого простору (або клітинок, якщо йдеться про ґратку)
  • знаходження часткового заповнення з мінімальною кількістю фігур таким чином, щоб додати ще хоча б одну фігуру було вже неможливо
Коли підходить цей алгоритм?
  • Чи потрібно замостити/заповнити ґратку фігурами (доміно, тримі тощо) і підрахувати способи або оптимізувати заповнення?
  • Чи одна зі сторін ґратки MM мала (зазвичай M15..20M \lesssim 15..20), щоб маска рядка з 2M2^M станів вмістилася в пам'ять і час?
  • Чи стан рядка повністю визначається межею («профілем») із попереднім рядком, а не глобальною конфігурацією всієї ґратки? (якщо MM велике й обидві сторони великі → задача стає значно складнішою і потребує інших методів)

Задача «Паркет»

Опис задачі. Задано ґратку розміру N×MN \times M. Знайдіть кількість способів заповнити цю ґратку фігурами розміру 2×12 \times 1 (жодна клітинка не повинна лишитися незаповненою, і фігури не повинні перекриватися).

Нехай стан ДП буде таким: dp[i,mask]dp[i, mask], де i=1,Ni = 1, \ldots N і mask=0,2M1mask = 0, \ldots 2^M - 1.

ii позначає кількість рядків у поточній ґратці, а maskmask — це стан останнього рядка поточної ґратки. Якщо jj-й біт маски maskmask дорівнює 00, то відповідна клітинка заповнена, інакше вона незаповнена.

Очевидно, що відповіддю до задачі буде dp[N,0]dp[N, 0].

Ми будуватимемо стан ДП, перебираючи кожне i=1,Ni = 1, \cdots N і кожну маску mask=0,2M1mask = 0, \ldots 2^M - 1, і для кожної маски maskmask ми робитимемо переходи лише вперед, тобто ми додаватимемо фігури до поточної ґратки.

Реалізація

int n, m;
vector < vector<long long> > dp;


void calc (int x = 0, int y = 0, int mask = 0, int next_mask = 0)
{
if (x == n)
return;
if (y >= m)
dp[x+1][next_mask] += dp[x][mask];
else
{
int my_mask = 1 << y;
if (mask & my_mask)
calc (x, y+1, mask, next_mask);
else
{
calc (x, y+1, mask, next_mask | my_mask);
if (y+1 < m && ! (mask & my_mask) && ! (mask & (my_mask << 1)))
calc (x, y+2, mask, next_mask);
}
}
}


int main()
{
cin >> n >> m;

dp.resize (n+1, vector<long long> (1<<m));
dp[0][0] = 1;
for (int x=0; x<n; ++x)
for (int mask=0; mask<(1<<m); ++mask)
calc (x, 0, mask, 0);

cout << dp[n][0];

}

Задачі для практики

Джерела