Bitmask DP — Templates & Analysis & Examples (Reference)

发表于 2020-09-05 22:39 1074 字 6 min read

cos avatar

cos

FE / ACG / 手工 / 深色模式强迫症 / INFP / 兴趣广泛养两只猫的老宅女 / remote

状压DP是通过二进制位表示状态,将问题中的状态压缩为一个整数来处理,常用位运算实现状态的转移与判断。文章以“Hie with the Pie”和“Travelling”为例,分别讲解了使用状压DP求解最短路径覆盖和城市访问限制问题,其中前者用Floyd预处理最短路,后者引入三进制状压处理每个城市最多访问两次的情况。

This article has been machine-translated from Chinese. The translation may contain inaccuracies or awkward phrasing. If in doubt, please refer to the original Chinese version.

The bitmask explanation section references blog post Bitmask DP Detailed Explanation (Bit Operations)

I. What is Bitmask DP?

This is a quote

Bitmask DP compresses states into binary representation for storage. For example, in a matrix, a row's state is compressed into a binary string. In dp[S][v], S can represent the set of already-visited vertices, and v represents the current vertex. S represents a state (in binary). (11001)2 means vertices {0, 3, 4} have been visited. The decimal value of (11001)2 is 25, so when S is 25, it means vertices {0, 3, 4} have been visited. If there are 5 vertices in total (labeled 0, 1, 2, 3, 4), then when all vertices are visited, S would be (11111)2.

II. Common Bit Operations

Bit operations reference
Here are some examples of the operations above. Using (11001)2 as mentioned above, which represents having visited vertices 0, 3, and 4. If we next want to visit vertex 2, we can perform the following operations: 1. What is the 3rd bit? Use "extract the k-th bit of binary number x": y = x >> (k-1) & 1, y = (110)2 & 1 = 0. 2. Set the 3rd bit to 1: y = x | (1<<(k-1)), y = (11001)2 | (100)2 = (11101)2. So after visiting vertex 2 and adding it to set S, S becomes (11101)2 = 29.

III. Example Problems

A - Hie with the Pie

Problem Statement

Given the travel times between all locations (which may differ in each direction), find the minimum time to deliver orders to all locations and return to the pizza shop (labeled 0).

Approach

First use the Floyd algorithm to get the shortest path matrix. Use S to represent the state. dp[S][i] represents the minimum time to reach location i. From S, we can determine which cities have been delivered to. S = 1<<N-1 represents the state where all deliveries are complete (all N bits are 1).

Code

// A-Hie with the Pie
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
#define div 1000000007
const int maxn = 12;
const int inf  = 0x3f3f3f;
int N,S,ans;
int dis[maxn][maxn];
int dp[1<<maxn][maxn];
void Floyd() {
    for(int k = 0; k <= N; ++k) {
        for(int i = 0; i <= N; ++i) {
            for(int j = 0; j <= N; ++j) {
                if(dis[i][j] > dis[i][k] + dis[k][j]) {
                    dis[i][j] = dis[i][k] + dis[k][j];
                }
            }
        }
    }
}
int main(){
    while(scanf("%d", &N) && N) {
        for(int i = 0; i <= N; ++i) {
            for(int j = 0; j <= N; ++j) {
                scanf("%d", &dis[i][j]);
            }
        }
        Floyd();
        for(int S = 0; S < (1<<N); ++S) {//All states of S
            for(int i = 1; i <= N; ++i) {//All delivery locations
                if(S & ( 1<<(i-1) )) {  //Need to deliver to location i
                    if(S == ( 1<<(i-1) )) {//If S hasn't delivered to any location yet
                        dp[S][i] = dis[0][i];
                        break;
                    } else {
                        dp[S][i] = inf;
                        for(int j = 1; j <= N; ++j) {
                            if(j == i) continue;
                            //Enumerate already-delivered locations other than the current one, check if time is shorter
                            if(S&( 1<<(j-1) )) {
                                dp[S][i] = min(dp[S][i],dp[S^(1<<(i-1))][j] + dis[j][i]);
                            }


                        }
                    }
                }
            }
        }
        ans = inf;
        for(int i = 1; i <= N; ++i) {
            ans = min(ans, dp[(1<<N)-1][i] + dis[i][0]);
        }
        printf("%d\n",ans);
    }
    return 0;
}

B - Travelling

Problem Statement

Given N cities and M road costs, Mr. Acmer wants to visit all cities with each city visited at most twice. Find the minimum cost, or output -1 if no such route exists.

Approach

Ternary bitmask DP. Each digit represents: 0 = not visited, 1 = visited once, 2 = visited twice. We use cnt[i][j] to represent the j-th digit (from right) of i in base 3, and three[i] represents the weight of the i-th ternary digit, similar to 1<<i in binary. Finally, if all cities are visited and none is visited more than twice, compare with ans.

Code

// B - Travelling
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
#define div 1000000007
const int maxn = 12;
const int maxt = 59049;
const int inf  = 0x3f3f3f;
int N,M,S,ans;
int three[maxn];//three[i] is the max value of the i-th ternary digit, similar to 1<<i in binary
int map[maxn][maxn];
int cnt[maxt][maxn];//cnt[i][j] represents the j-th digit (from right) of i in base 3
int dp[maxt][maxn];//dp[i][j] represents the total cost after visiting city j in state i
void init() {
    memset(dp, -1, sizeof(dp));
    for(int i = 1; i <= N; ++i) {
        for(int j = 1; j <= N; ++j) {
            map[i][j] = inf;
        }
        map[i][i] = 0;
    }
}
int main(){
    int x = 3;
    three[0] = 0;
    three[1] = 1;
    for(int i = 2; i < maxn; ++i) {
        three[i] = x;
        x *= 3;
    }
    for(int i = 0; i < three[maxn-1]; ++i) {
        int now = i;
        for(int j = 1; j <= 10 && now; ++j) {
            cnt[i][j] = now % 3;
            now /= 3;
        }
    }
    while(~scanf("%d %d", &N, &M)) {
        init();
        for(int i = 1; i <= M; ++i) {
            int u,v,w;
            scanf("%d %d %d", &u, &v, &w);
            if(w < map[u][v]) {
                map[u][v] = map[v][u] = w;
            }
        }
        for(int i = 1; i <= N; ++i) {
            dp[three[i]][i] = 0;
        }
        int ans = inf;
        for(int S = 0; S < three[maxn-1]; ++S) {//All states of S
            bool allvis = true;
            for(int i = 1; i <= N; ++i) {//Enumerate each city
                if(cnt[S][i] == 0)   //This city hasn't been visited in this state
                    allvis = false;
                if(dp[S][i] == -1) continue;
                for(int j = 1; j <= N; ++j) {   //Enumerate the next city to visit
                    if(i == j || map[i][j] == inf || cnt[S][j] >= 2)
                        continue;
                    int nS = S+three[j];//Next state
                    if(dp[nS][j] == -1 || dp[S][i] + map[i][j] < dp[nS][j]) { //Lower total cost for the next city
                        dp[nS][j] = dp[S][i] + map[i][j];
                    }
                }
            }
            if(allvis) {
                for(int i = 1; i <= N; ++i) {
                    if(dp[S][i] != -1) {
                        ans = min(ans, dp[S][i]);
                    }
                }
            }
        }
        if(ans == inf) printf("-1\n");
        else printf("%d\n",ans);
    }
    return 0;
}

喜欢的话,留下你的评论吧~

© 2020 - 2026 cos @cosine
Powered by theme astro-koharu · Inspired by Shoka