状态压缩 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\) 位:

\[\left[\frac{\text{state}}{n^i}\right] \text{ mod } n \]

集合操作

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)
复杂度

\[T(n) = \sum_{i = 0}^n {n \choose i} (2^i - 1) = 3^n-2^n = \Theta(3^n - 2^n) = \Omicron(3^n) \]

常见问题

旅行商 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)\)

posted @ 2026-02-12 20:50  Yangyihao  阅读(14)  评论(0)    收藏  举报