状态压缩 DP
题目范围小(问题多为
NP-Hard类问题)
概念
当一个 \(dp[i,s_1,s_2,s_3,...]\) ,其中 \(s_i\) 表示状态时,可以使用状态压缩,将 \(\{s_i\}\) 压缩成 \(\text{state}\)
位运算操作
.
取出第 \(i\) 位:(state >> i) & 1 \(\longleftrightarrow\) state & (1 << i)
将第 \(i\) 位改成 1 :state = state | (1 << i)
或:
if (! ((state >> i) & 1)) state += (1 << i)
将第 \(i\) 位改成 \(0\):state = state & ~(1 << i)
或:
if (((state >> i) & 1)) state -= (1 << i);
对于 \(n\) 进制:
取出第 \(i\) 位:
集合操作
s1 和 s2 是两个集合:
\(s_1 \cup s_2\) \(\longleftrightarrow\) s1 | s2
\(s_1\cap s_2\) \(\longleftrightarrow\) s1 & s2
\(s_1 \text{ \ } s_2\) \(\longleftrightarrow\) s1 ^ s2
子集枚举
a & b,\(b \le a\) ,返回 \(a\) 的子集
for (int state = 0; state < (1 << n); state++)
for (int subset = state; subset; subset = (subset - 1) & state)
复杂度
常见问题
旅行商 Traveling Salesman Problem
概括
最短 Hamilton 路径
状态
\(dp[\text{state}, i]\) :已走了 \(\text{state}\) 个点,当前走到了 \(i\) 个点
转移:
for (int state = 1; state <= (1 << n) - 1; state++)
for (int j = 0; j < n; j++) {
if (!(state & (1 << j))) continue;
for (int k = 0; k < n; k++) {
if (j == k and !(state & (1 << k))) continue;
dp[state][j] = min(dp[state][j], dp[state ^ (1 << j)][k] + adjMatrix[k][j]);
}
}
例题
P1171 售货员的难题
纯板子题
for (int state = 1; state <= (1 << n) - 1; state++)
for (int j = 0; j < n; j++) {
if (!(state & (1 << j))) continue;
for (int k = 0; k < n; k++) {
if (j == k and !(state & (1 << k))) continue;
dp[state][j] = min(dp[state][j], dp[state ^ (1 << j)][k] + adjMatrix[k][j]);
}
}
int finalState = (1 << n) - 1;
int minValue = INF;
for (int i = 0; i < n; i++) minValue = min(minValue, dp[finalState][i] + adjMatrix[i][0]);
cout << minValue << "\n";
P10963 Islands and Bridges
稍微加入了一些变化
这时,我们便需要再枚举节点 u 的上上个节点,判断是否形成三角形
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (i != j && adjMatrix[i][j]) {
dp[(1 << i) | (1 << j)][i][j] = v[i] + v[j] + v[i] * v[j];
total[(1 << i) | (1 << j)][i][j] = 1;
}
ll maxValue = -INF;
for (int state = 0; state < (1 << n); state++)
for (int i = 0; i < n; i++) {
if (!((state >> i) & 1)) continue;
for (int j = 0; j < n; j++) {
if (!((state >> j) & 1) || i == j || !adjMatrix[i][j]) continue;
if (dp[state][i][j] == -INF) continue;
for (int k = 0; k < n; k++) {
if (((state >> k) & 1) || !adjMatrix[j][k] || k == i || k == j) continue;
int newState = state | (1 << k);
ll value = v[k] + v[k] * v[j];
if (adjMatrix[i][k]) value += v[i] * v[j] * v[k];
ll newVal = dp[state][i][j] + value;
if (newVal > dp[newState][j][k]) {
dp[newState][j][k] = newVal;
total[newState][j][k] = total[state][i][j];
} else if (newVal == dp[newState][j][k])
total[newState][j][k] += total[state][i][j];
}
}
}
ll count = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
if (i != j && adjMatrix[i][j]) {
if (maxValue < dp[(1 << n) - 1][i][j]) {
maxValue = dp[(1 << n) - 1][i][j];
count = total[(1 << n) - 1][i][j];
} else if (maxValue == dp[(1 << n) - 1][i][j]) {
count += total[(1 << n) - 1][i][j];
}
}
}
if (maxValue == -INF) {
cout << "0 0\n";
return;
}
cout << maxValue << " " << (count >> 1) << "\n";
* 对于 Hack 数据:当 \(n = 1\) 时:
if (n == 1) {
cout << v[0] << " 1\n";
return ;
}
TSP 还有一种解法:模拟退火
比较优的解法是线性规划,可以处理当 \(n \ge 1000\) 时的情况
子集枚举 ??
这真的算一种吗?
还不是纯板子:
for (int state = 0; state < (1 << n); state++)
for (int subset = state; subset; subset = (subset - 1) & state)
只不过复杂度 \(\Omicron(3^n)\) ,有亿点不友好
例题
还是来一道吧
P3052 Cows in a Skyscraper G
当然了朴素的枚举子集做法过不了
for (int i = 0; i <= (1 << n) - 1; i++) dp[i] = INF;
dp[0] = 0;
for (int state = 0; state < (1 << n); state++)
for (int i = 0; i < n; i++)
if ((state >> i) & 1)
setSum[state] += cow[i];
for (int state = 0; state < (1 << n); state++)
for (int subSet = state; subSet ; subSet = (subSet - 1) & state) {
int totalW = setSum[subSet];
if (totalW > maxW) continue;
dp[state] = min(dp[state], dp[state ^ subSet] + 1);
}
cout << dp[(1 << n) - 1] << "\n";
所以说考虑优化
我们不妨把电梯数放进状态里面:
for (int state = 0; state < (1 << n); state++)
for (int i = 1; i <= n; i++) {
if (dp[i][state] >= INF) continue;
for (int j = 0; j < n; j++) {
if ((state >> j) & 1) continue;
if (dp[i][state] + cow[j] <= maxW) dp[i][state | (1 << j)] = min(dp[i][state | (1 << j)], dp[i][state] + cow[j]);
if (dp[i][state] + cow[j] > maxW) dp[i + 1][state | (1 << j)] = min(dp[i + 1][state | (1 << j)], cow[j]);
}
}
很简单
初始化:
for (int i = 0; i <= (1 << n) - 1; i++) for (int j = 0; j <= n; j++) dp[j][i] = INF;
for (int i = 0; i <= n; i++) dp[i][0] = 0;
复杂度 \(\Omicron(2^n n^2)\)
还能再优化:
将电梯那一维去掉
pair<int, int> dp[(1 << 20) + 5];
没错,用 pair<int, int> 来存储,.first 表示答案,.second 表示最后一架电梯的载”人“量
转移:
for (int mask = 0; mask < (1 << n); mask++) { // 用 mask 应该看的懂吧
auto [elev, weight] = dp[mask];
if (elev == INF) continue;
for (int i = 0; i < n; i++) {
if (mask >> i & 1) continue;
// int nmask = mask | (1 << i);
if (weight + w[i] <= W) {
auto& [ne, nw] = dp[mask | (1 << i)];
if (elev < ne) {
ne = elev;
nw = weight + w[i];
} else if (elev == ne && weight + w[i] < nw)
nw = weight + w[i];
} else {
auto& [ne, nw] = dp[mask | (1 << i)];
if (elev + 1 < ne) {
ne = elev + 1;
nw = w[i];
} else if (elev + 1 == ne && w[i] < nw)
nw = w[i];
}
}
}
复杂度:\(\Omicron(2^nn)\)

浙公网安备 33010602011771号