E - 料理の順番 / Order of Dishes 解説 by admin
gemini-3.5-flash-thinkingOverview
This problem asks us to maximize the total satisfaction obtained by cooking some dishes in an appropriate order while avoiding given failure conditions. Since the number of dishes \(N\) is at most \(19\), which is very small, we can solve this quickly and accurately within the time limit using Bit DP and Sum over Subsets DP (SOS DP / Fast Zeta Transform).
Analysis
1. Naive Approach and Its Limitations
If we explore all possible orderings of cooking dishes, there are at most \(N!\) orderings in the worst case. When \(N = 19\), \(19! \approx 1.2 \times 10^{17}\), making brute-force search far too slow to meet the time limit.
2. State Compression (From Orderings to Sets)
A failure condition \((p, S)\) for dish \(p\) means “at the time you start cooking dish \(p\), all dishes in set \(S\) have already been completed.” The key insight here is that only the set of “which dishes have been completed so far” matters, and the order in which they were completed is irrelevant.
Therefore, Bit DP is effective, where we manage the state as “the set of dishes completed so far” rather than their ordering. There are only \(2^N\) possible sets (\(524,288\) when \(N=19\)), which is fast enough to process.
3. Fast Evaluation of Failure Conditions (Introducing Fast Zeta Transform)
Consider determining whether dish \(p\) can be cooked next, given that the set \(T\) of dishes has already been completed. A failure condition is triggered when there exists a failure condition \((p, S)\) such that \(S \subseteq T\) (\(S\) is a subset of \(T\)).
If we try to evaluate this naively, we would need to check all failure conditions for each state \(T\), resulting in enormous computational cost.
Instead, we precompute bad[T] (a bitmask) representing “the set of dishes that cannot be cooked when the set of completed dishes is \(T\)”.
- As initialization, for each failure condition \((p, S)\), set the \(p\)-th bit of
bad[S]to \(1\). - If \(S \subseteq T\), then naturally dish \(p\) cannot be cooked when \(T\) is completed either. Therefore, for all \(S \subseteq T\), we need to merge (logical OR
|) the information ofbad[S]intobad[T].
This corresponds to propagating values in the superset direction. Doing this naively takes \(O(3^N)\), but using Fast Zeta Transform (SOS DP), it can be done efficiently in \(O(N \cdot 2^N)\).
Algorithm
The specific steps are as follows.
Step 1: Initialization of Failure Conditions
Convert each dish number to 0-indexed (from 0 to N-1) for processing.
Initialize an array bad of length \(2^N\) to \(0\), and for each input failure condition \((p, S)\), create a bitmask mask representing set \(S\), and set the \(p\)-th bit of bad[mask] to \(1\).
Step 2: Propagation of Failure Conditions via Fast Zeta Transform (SOS DP)
Using the following transition, accumulate all failure conditions from subsets for every mask.
\[ \text{if } (mask \text{'s } i\text{-th bit is } 1): \quad bad[mask] \leftarrow bad[mask] \lor bad[mask \setminus \{i\}] \]
By performing this for all \(i \in [0, N-1]\) and all \(mask \in [0, 2^N-1]\) in order, the correct bad table is completed.
Step 3: Precomputation of Satisfaction Sums for Each Set
Compute sum_A[mask], the total satisfaction of all dishes included in each set mask.
This can be computed by adding \(A_x\) to the satisfaction sum of the set obtained by removing the lowest set bit \(x\) from mask, achieving \(O(1)\) per transition and \(O(2^N)\) overall.
Step 4: Search via Bit DP
Let dp[mask] be a boolean representing “whether it is possible to complete the set of dishes mask in some order that does not violate any failure conditions.”
- Initialization:
dp[0] = true(the state of having cooked nothing is always possible), maximum satisfactionmax_ans = 0. - Transition: Loop
maskfrom \(0\) to \(2^N-1\) in ascending order.- If
dp[mask]isfalse, skip it. - If
dp[mask]istrue, updatemax_answith the current satisfactionsum_A[mask]. - Determine the candidate dishes
next_candidatesthat can be cooked next.- Dishes not yet cooked:
~mask - Dishes safe to cook in the current situation:
~bad[mask] - Therefore,
next_candidates = (~mask) & (~bad[mask])(masking off unnecessary upper bits).
- Dishes not yet cooked:
- For each dish \(x\) with a set bit in
next_candidates, updatedp[mask | (1 << x)] = true.
- If
Complexity
Time Complexity
- Reading failure conditions: \(O(M + \sum c_q)\)
- Fast Zeta Transform: \(O(N \cdot 2^N)\)
- Satisfaction precomputation: \(O(2^N)\)
- DP transitions: For each
mask, we explore candidate dishes to transition to (at most \(N\)), so \(O(N \cdot 2^N)\)
The overall time complexity is \(O(M + \sum c_q + N \cdot 2^N)\). When \(N = 19\), \(N \cdot 2^N \approx 10^7\) simple bit operations, which runs in approximately \(0.1\) seconds—very fast relative to the typical time limit of 2.0 seconds.
Space Complexity
- The arrays
bad,sum_A, anddpeach have size \(2^N\).
The overall space complexity is \(O(2^N)\). When \(N = 19\), each array requires only a few megabytes of memory, which is well within the memory limit.
Implementation Notes
1. Optimization Using __builtin_ctz
The C++ built-in function __builtin_ctz (Count Trailing Zeros) counts the number of consecutive 0s from the right (least significant bit) of a bit string.
Using this, we can obtain the position of the least significant set bit in a bitmask in \(O(1)\).
* During satisfaction precomputation: int x = __builtin_ctz(mask); quickly identifies one dish contained in mask.
* During DP transitions: We extract dishes \(x\) one by one from next_candidates and clear that bit with next_candidates ^= (1 << x), allowing us to efficiently loop over only the positions with set bits without any waste.
Source Code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// Optimize standard I/O operations for speed
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N, M;
if (!(cin >> N >> M)) return 0;
vector<long long> A(N);
for (int i = 0; i < N; ++i) {
cin >> A[i];
}
// bad[mask] stores a bitmask of dishes that cannot be cooked if 'mask' is the set of completed dishes.
vector<int> bad(1 << N, 0);
for (int i = 0; i < M; ++i) {
int p, c;
cin >> p >> c;
--p; // Convert to 0-indexed
int mask = 0;
for (int j = 0; j < c; ++j) {
int x;
cin >> x;
--x; // Convert to 0-indexed
mask |= (1 << x);
}
bad[mask] |= (1 << p);
}
// Fast Zeta Transform (SOS DP) to propagate failure conditions to all supersets
for (int i = 0; i < N; ++i) {
for (int mask = 0; mask < (1 << N); ++mask) {
if ((mask >> i) & 1) {
bad[mask] |= bad[mask ^ (1 << i)];
}
}
}
// Precompute the sum of satisfaction for each subset of dishes
vector<long long> sum_A(1 << N, 0);
for (int mask = 1; mask < (1 << N); ++mask) {
int x = __builtin_ctz(mask);
sum_A[mask] = sum_A[mask ^ (1 << x)] + A[x];
}
// dp[mask] is true if the subset of dishes represented by 'mask' can be cooked in some valid order
vector<bool> dp(1 << N, false);
dp[0] = true;
long long max_ans = 0; // Minimum satisfaction is 0 (by cooking 0 dishes)
for (int mask = 0; mask < (1 << N); ++mask) {
if (!dp[mask]) continue;
max_ans = max(max_ans, sum_A[mask]);
// Find candidates that are not yet cooked (~mask) and are safe to cook (~bad[mask])
int next_candidates = (~mask) & (~bad[mask]) & ((1 << N) - 1);
while (next_candidates > 0) {
int x = __builtin_ctz(next_candidates);
dp[mask | (1 << x)] = true;
next_candidates ^= (1 << x);
}
}
cout << max_ans << "\n";
return 0;
}
This editorial was generated by gemini-3.5-flash-thinking.
投稿日時:
最終更新: