B - シャンパンタワー / Champagne Tower 解説 by admin
gemini-3.5-flash-thinkingOverview
This problem involves simulating the process of pouring champagne from above into glasses arranged in a row, where overflow from one glass sequentially flows down to the glass below it. The goal is to perform this simulation efficiently.
A naive simulation would repeatedly scan glasses that are already full, making it impossible to meet the time limit. Therefore, by using Union-Find (DSU: Disjoint Set Union) to quickly detect “the next glass that is not yet full,” we can perform the simulation efficiently.
Analysis
Naive Approach and Its Limitations
The simplest method is to start from the 1st glass each time champagne is poured, and whenever the capacity is exceeded, add the overflow to the next level, repeating this process.
However, when a large amount of champagne is poured into a state where many glasses are already full, we end up skipping “already full glasses” one by one each time. In the worst case, a single pouring operation takes \(O(N)\) time, and with \(Q\) queries, the total time complexity becomes \(O(NQ)\). Since \(N, Q \le 2 \times 10^5\), this will result in a Time Limit Exceeded (TLE) verdict.
Key to Optimization: Jumping to “the Next Non-Full Glass”
For optimization, we consider a method to “quickly skip glasses that are already full.”
When glass \(i\) becomes full, the next time champagne flows into glass \(i\), it should immediately flow to “the nearest glass below glass \(i\) that is not yet full.”
For managing such “representatives of groups (in this case, the nearest glass with available capacity),” Union-Find (DSU) is ideal.
Specifically, we manage it as follows: - The moment glass \(i\) becomes full, we connect (unite) glass \(i\) and glass \(i+1\). - When connecting, we always merge so that the one with the larger index (the lower glass) becomes the root (representative).
By doing this, when champagne is poured into (or flows into) glass \(i\), simply calling find(i) on the DSU allows us to identify “the glass with the smallest index that is not yet full and should receive the champagne next” in nearly \(O(1)\) time.
Algorithm
1. DSU Initialization
Prepare a DSU of size \(N + 2\). - Elements from \(1\) to \(N\) correspond to each glass. - \(N + 1\) represents a sentinel for “the bottom of the tower (the ground).” It serves the role of catching champagne that overflows from the lowest glass, with infinite capacity (or simply a place to discard overflow). - In the initial state, since no glass is full, each one is the root of its own independent set.
2. Processing Operation \(1\) (Pouring)
When an instruction to pour \(V\) milliliters of champagne into the 1st glass arrives, repeat the following steps:
- Obtain the index of the glass that champagne should currently flow into with
curr = dsu.find(1). - While
curr <= nand \(V > 0\), execute the following:- Calculate the current remaining capacity of glass
curr:space = C[curr] - A[curr]. - Case A: When \(V \ge \text{space}\) (the glass becomes full)
- Glass
currbecomes full. - Subtract
spacefrom \(V\) (update the remaining champagne amount), and setA[curr] = C[curr]. - Since glass
curris now full, performdsu.unite(curr, curr + 1)to connect the path to the next glass. - Update the next glass to pour into with
curr = dsu.find(curr).
- Glass
- Case B: When \(V < \text{space}\) (the glass does not become full)
- All \(V\) can be poured into glass
curr. - Add \(V\) to
A[curr], and set \(V = 0\). - Exit the loop.
- All \(V\) can be poured into glass
- Calculate the current remaining capacity of glass
3. Processing Operation \(2\) (Query)
Output the current amount A[k] in glass \(k\) as is.
Complexity
Time Complexity: \(O((N + Q) \alpha(N))\)
- The event of “a glass becoming full” occurs at most \(N\) times across the entire tower.
- Since
dsu.uniteis called only when a glass becomes full, the total number ofuniteexecutions is at most \(N\). - Therefore, the total number of iterations of the
whileloop in operation \(1\) is bounded by at most \(N + Q\) across all queries. - Each DSU operation (
find,unite) can be performed in \(O(\alpha(N))\) (nearly constant time) using the inverse Ackermann function \(\alpha\), so the overall time complexity is \(O((N + Q) \alpha(N))\), which runs extremely fast.
Space Complexity: \(O(N)\)
- \(O(N)\) memory is used for the glass capacity array
C, the current champagne amount arrayA, and the array managing the DSU’s parent nodes.
Implementation Notes
Forcing the merge direction in DSU: In a typical DSU, “union by size” or similar techniques are used to keep the tree height low, but in this problem, “the one with the larger index must always become the parent.” In the
unitefunction, implement it so that whenroot_i < root_j, we always setparent[root_i] = root_j.Sentinel at index \(N+1\): When the lowest glass (at level \(N\)) becomes full, it is merged with \(N+1\) below it. To prevent out-of-bounds array access, the DSU size must be allocated as \(N+2\).
Fast I/O: Since the number of queries is large, in C++ it is recommended to use fast
std::cin(ios_base::sync_with_stdio(false); cin.tie(NULL);) and to use"\n"instead ofstd::endl(which causes a buffer flush) when outputting newlines.Source Code
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
// Disjoint Set Union (DSU) to find the next available non-full glass
struct DSU {
vector<int> parent;
DSU(int n) {
parent.resize(n);
iota(parent.begin(), parent.end(), 0);
}
int find(int i) {
if (parent[i] == i)
return i;
return parent[i] = find(parent[i]);
}
void unite(int i, int j) {
int root_i = find(i);
int root_j = find(j);
if (root_i != root_j) {
// Always point to the larger index (towards the bottom of the tower)
if (root_i < root_j) {
parent[root_i] = root_j;
} else {
parent[root_j] = root_i;
}
}
}
};
int main() {
// Optimize standard I/O operations for speed
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, q;
if (!(cin >> n >> q)) return 0;
vector<long long> C(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> C[i];
}
vector<long long> A(n + 1, 0); // Current amount of champagne in each glass
DSU dsu(n + 2); // Size n + 2 to handle the boundary case at level n + 1
for (int i = 0; i < q; ++i) {
int type;
cin >> type;
if (type == 1) {
long long v;
cin >> v;
int curr = dsu.find(1);
while (curr <= n && v > 0) {
long long space = C[curr] - A[curr];
if (v >= space) {
v -= space;
A[curr] = C[curr];
dsu.unite(curr, curr + 1);
curr = dsu.find(curr);
} else {
A[curr] += v;
v = 0;
}
}
} else {
int k;
cin >> k;
cout << A[k] << "\n";
}
}
return 0;
}
This editorial was generated by gemini-3.5-flash-thinking.
投稿日時:
最終更新: