Bitmask DP с состоянием 3^n: как считать ДП, где каждый элемент в одном из трёх состояний?
Встретил задачу, где каждый из объектов может быть в одном из трёх состояний (например: «не использован», «открыт», «закрыт»), и переход зависит от полной конфигурации. Битовая маска даёт только 0/1 на бит. Как кодировать состояние из трёх вариантов и сколько это стоит?
2 ответа
Когда у элемента три состояния, естественное кодирование — число в троичной системе: конфигурация = число от до , где i-я троичная цифра ∈ {0,1,2} — состояние i-го объекта.
Извлечение/установка троичной цифры:
int n; // <= 12, т.к. 3^12 ~ 531441
vector<int> pow3(n + 1);
pow3[0] = 1;
for (int i = 1; i <= n; ++i) pow3[i] = pow3[i - 1] * 3;
// получить состояние объекта i из конфигурации mask
auto get = [&](int mask, int i){ return (mask / pow3[i]) % 3; };
// установить объекту i состояние v (0..2)
auto setv = [&](int mask, int i, int v){
int cur = get(mask, i);
return mask + (v - cur) * pow3[i];
};
vector<long long> dp(pow3[n], 0);
dp[0] = 1; // все объекты в состоянии 0
for (int mask = 0; mask < pow3[n]; ++mask) {
if (dp[mask] == 0) continue;
// ... переходы, меняющие состояние объектов через setv ...
}
Число состояний . Если переход перебирает объект () — итого . Для : , умножить на 12 → — быстро. Но рост резкий: даёт , а — уже (TLE). Поэтому троичные маски — для маленьких .
Альтернатива троичному кодированию — две битовые маски (по маске на каждое из двух «активных» состояний, третье = «ни в одной»). Тогда состояние = пара (maskA, maskB) с инвариантом maskA & maskB == 0. Битовые операции быстрее деления/модуля, которые нужны для троичных цифр (/ и % по pow3 — дорогие операции).
Компромисс: троичное кодирование компактнее по памяти ( ячеек против потенциально , если хранить пару наивно) и нагляднее, но %// в горячем цикле бьют по константе. Для разница не критична — бери что читаемее. Для больших и плотного перебора — две маски с битовыми операциями выигрывают по константе.