Prerequisites: Bitmask DP, Traveling Salesperson Problem (TSP), Chebyshev Distance
We are given a grid where we can move in all 8 directions (horizontally, vertically, and diagonally). This implies that the shortest distance between any two cells
Proof: To minimize moves, we should move diagonally as much as possible because one diagonal move changes both our X and Y coordinates simultaneously. We do this until we align with our target on either the X or Y axis, after which we move in a straight line. The total number of moves will always be bounded by the maximum of the absolute differences of their coordinates.
Let
Because
State Definition:
Let dp[mask][i] be the minimum number of moves required to visit the subset of gold pieces represented by mask, ending at gold piece
-
maskis an integer where the$j$ -th bit is 1 if the$j$ -th gold piece has been visited, and 0 otherwise. -
$i$ is the index of the last visited gold piece.
Base Cases:
Initially, we only have one gold piece in our path. For every gold piece
Transitions:
To compute dp[mask][i], we must transition from a previous state where we had visited all nodes in the mask except node mask, we iterate over every other node mask (
Final Answer:
Once the DP table is fully populated, the final answer requires us to return to the starting position 'x'. We take the minimum over all possible last-visited gold pieces:
A loose upper bound for the time complexity is
At first glance, this might look too slow. The maximum number of gold pieces is
The Optimization: We can drastically reduce the constant factor by only iterating through the active bits (nodes that are actually present in the current mask).
The exact number of operations with this optimization: For any mask that has exactly
Summing this up for all possible subset sizes gives us our exact worst-case transitions:
If we plug in
Across the maximum of 100 test cases, this tight bound results in roughly 172 million operations in total
Solution code (C++)
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int get_dist(pair<int, int> a, pair<int, int> b) {
return max(abs(a.first - b.first), abs(a.second - b.second));
}
void solve(int tc) {
int r, c;
cin >> r >> c;
pair<int, int> start;
vector<pair<int, int>> gold;
for (int i = 0; i < r; ++i) {
string row;
cin >> row;
for (int j = 0; j < c; ++j) {
if (row[j] == 'x') {
start = {i, j};
} else if (row[j] == 'g') {
gold.push_back({i, j});
}
}
}
int K = gold.size();
if (K == 0) {
cout << "Case " << tc << ": 0\n";
return;
}
vector<vector<int>> dp(1 << K, vector<int>(K, INF));
for (int i = 0; i < K; ++i) {
dp[1 << i][i] = get_dist(start, gold[i]);
}
for (int mask = 1; mask < (1 << K); ++mask) {
vector<int> active;
for (int i = 0; i < K; ++i) {
if ((mask >> i) & 1) {
active.push_back(i);
}
}
if (active.size() <= 1) continue;
for (int i : active) {
int prev_mask = mask ^ (1 << i);
for (int j : active) {
if (i == j) continue;
int dist = get_dist(gold[j], gold[i]);
dp[mask][i] = min(dp[mask][i], dp[prev_mask][j] + dist);
}
}
}
int ans = INF;
int full_mask = (1 << K) - 1;
for (int i = 0; i < K; ++i) {
int return_dist = get_dist(gold[i], start);
ans = min(ans, dp[full_mask][i] + return_dist);
}
cout << "Case " << tc << ": " << ans << "\n";
}
int main() {
int t;
if (cin >> t) {
for (int i = 1; i <= t; ++i) {
solve(i);
}
}
return 0;
}