Official

E - 工場の解体作業 / Factory Demolition Work Editorial by admin

gemini-3.5-flash-high

Overview

This problem asks us to find the optimal order to demolish \(N\) given buildings to minimize the total cost. Since the cost of demolishing a building depends on which buildings have already been demolished, the order of demolition is crucial. Given the constraint \(N \le 19\), we can find the optimal solution quickly using Bit DP (Dynamic Programming with bitsets/subsets as states).

Analysis

1. Naive Approach and Its Limitations

There are \(N!\) possible demolition orders in total. When \(N = 19\), \(19! \approx 1.2 \times 10^{17}\), so a naive brute-force search over all permutations will not run within the time limit.

2. State Compression (Transition to DP)

When we demolish a building \(v\) next, the only thing that affects its demolition cost is the set of already demolished buildings, regardless of the order in which they were demolished.

For example, if buildings \(\{1, 2\}\) are already demolished and we demolish building \(3\) next, whether the demolition order was \(1 \to 2\) or \(2 \to 1\), the value added to the cost of building \(3\) is \(W_{1,3} + W_{2,3}\), which is the same.

Therefore, we can design a Dynamic Programming (DP) approach with the following state: - State: The set \(S\) of already demolished buildings (represented as an \(N\)-bit integer). - Transition: Demolish a building \(v \notin S\) that has not been demolished yet.

3. Optimizing Cost Calculation

When demolishing building \(v\) from state \(S\), the demolition cost of building \(v\) is represented as: $\((\text{Demolition cost of building } v) = C_v + \sum_{u \in S} W_{u, v}\)$

If we naively compute this cumulative influence \(\sum_{u \in S} W_{u, v}\) during each transition, it takes \(O(|S|) = O(N)\) per transition. Since the number of DP states is \(2^N\) and there are \(N\) transitions from each state, the overall time complexity would be \(O(2^N \cdot N^2)\). For \(N=19\), this requires \(19^2 \cdot 2^{19} \approx 1.89 \times 10^8\) operations, which is very tight for the time limit.

To solve this, we can precompute the cumulative influence so that we can retrieve it in \(O(1)\) time. Specifically, we define sumW[S][v] as “the total influence from the buildings in set \(S\) to building \(v\)”. Let \(S_{\text{prev}}\) be the set \(S\) with any single element \(u \in S\) removed. We can efficiently precompute sumW using the following transition: $\(\text{sumW}[S][v] = \text{sumW}[S_{\text{prev}}][v] + W_{u, v}\)\( This allows us to find the values for all \)S\( and \)v\( in \)O(2^N \cdot N)$ time.


Algorithm

1. Table Definitions

  • sumW[S][v]: The cumulative cost influence on building \(v\) when all buildings in set \(S\) have already been demolished.
  • dp[S]: The minimum cumulative cost incurred so far when the set of demolished buildings is \(S\).

2. Precomputation (Cumulative Sum via DP)

Loop \(S\) in ascending order from \(1\) to \(2^N - 1\). Find the lowest set bit (element \(u\)) in \(S\) using functions like __builtin_ctz(S), and let \(S_{\text{prev}} = S \setminus \{u\}\). For all \(v\), update sumW as follows: $\(\text{sumW}[S][v] = \text{sumW}[S_{\text{prev}}][v] + W_{u, v}\)$

3. Bit DP Transitions

  1. Initialization:
    • dp[0] = 0 (the cost is 0 when no buildings have been demolished).
    • For all other \(S\), initialize dp[S] = INF (a sufficiently large value).
  2. Transitions: For each state \(S\) (\(0 \le S < 2^N\)), find a building \(v \notin S\) that has not been demolished yet. If we demolish building \(v\) next, the next state is \(S \cup \{v\}\), and the cost is updated as follows: $\(\text{dp}[S \cup \{v\}] = \min(\text{dp}[S \cup \{v\}], \; \text{dp}[S] + C_v + \text{sumW}[S][v])\)$
  3. Answer: The minimum cost to demolish all buildings is given by dp[(1 << N) - 1].

Complexity

  • Time Complexity: \(O(2^N \cdot N)\)

    • Precomputing sumW takes \(O(2^N \cdot N)\) time.
    • In the DP transitions, we perform \(N\) transitions from each of the \(2^N\) states in \(O(1)\) time, so the DP transition complexity is also \(O(2^N \cdot N)\).
    • When \(N = 19\), the total number of operations is approximately \(10^7\), which runs very quickly in C++ (taking only a few to tens of milliseconds).
  • Space Complexity: \(O(2^N \cdot N)\)

    • The size of the sumW array is \((2^N \times N)\), which is the bottleneck of the space complexity.
    • When \(N = 19\), using the int type (4 bytes) requires \(2^{19} \times 19 \times 4 \text{ bytes} \approx 40 \text{ MB}\) of memory, which is well within standard memory limits (such as 256MB or 1024MB).

Implementation Details

  • Leveraging Bitwise Operations:

    • Use S & (1 << v) to quickly check if building \(v\) is in set \(S\) (i.e., already demolished).
    • Use S | (1 << v) to add building \(v\) to set \(S\) (i.e., mark it as demolished).
    • Use __builtin_ctz(S) to quickly extract one element from set \(S\) in \(O(1)\) time.
  • Memory Allocation:

    • Since sumW and dp are very large arrays, defining them as local variables (on the stack) can cause a stack overflow. Make sure to allocate them in the global scope (static memory).

      Source Code

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int MAX_N = 20;
const long long INF = 1e18;

// Globally allocated to prevent stack overflow
int sumW[1 << MAX_N][MAX_N];
long long dp[1 << MAX_N];

int main() {
    // Optimize standard I/O operations for performance
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int N;
    if (!(cin >> N)) return 0;

    vector<long long> C(N);
    for (int i = 0; i < N; ++i) {
        cin >> C[i];
    }

    vector<vector<int>> W(N, vector<int>(N));
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < N; ++j) {
            cin >> W[i][j];
        }
    }

    int num_states = 1 << N;

    // Precompute sumW[S][v] = sum_{u in S} W[u][v] using DP on submasks
    for (int S = 1; S < num_states; ++S) {
        int u = __builtin_ctz(S);
        int S_prev = S ^ (1 << u);
        for (int v = 0; v < N; ++v) {
            sumW[S][v] = sumW[S_prev][v] + W[u][v];
        }
    }

    // Initialize DP table
    fill(dp, dp + num_states, INF);
    dp[0] = 0;

    // DP state transitions
    for (int S = 0; S < num_states; ++S) {
        if (dp[S] == INF) continue;
        for (int v = 0; v < N; ++v) {
            if (!(S & (1 << v))) {
                long long next_cost = dp[S] + C[v] + sumW[S][v];
                if (next_cost < dp[S | (1 << v)]) {
                    dp[S | (1 << v)] = next_cost;
                }
            }
        }
    }

    cout << dp[num_states - 1] << "\n";

    return 0;
}

This editorial was generated by gemini-3.5-flash-high.

posted:
last update: