B - シャンパンタワー / Champagne Tower Editorial by admin
gemini-3.5-flash-thinkingOverview
This problem involves simulating pouring champagne into a champagne tower consisting of \(N\) glasses arranged in series. When a glass becomes full, the overflow immediately flows down to the glass below.
A naive simulation would repeatedly scan through glasses that are already full, which would not meet the time limit. Instead, we use Union-Find (Disjoint Set Union) to efficiently find the “next glass with remaining capacity,” enabling an efficient simulation.
Analysis
Naive Approach (Simulation) and Its Limitations
Consider a straightforward method where, each time champagne is poured, we start from the 1st glass and repeat the process: “Is it full? If so, move to the next level.”
For example, suppose all glasses from level \(1\) to level \(N-1\) are already full. When a small amount of champagne is poured into the 1st glass, it passes through all glasses from level \(1\) to level \(N-1\) and ultimately reaches level \(N\). This requires \(N\) steps each time just for this determination.
Repeating such operations \(Q\) times results in a worst-case time complexity of \(O(NQ)\). In this problem, since \(N, Q \le 2 \times 10^5\), the worst case would require approximately \(4 \times 10^{10}\) operations, resulting in a Time Limit Exceeded (TLE) verdict.
Optimization Idea: “Skip Full Glasses”
For optimization, we consider the approach of “skipping glasses that are already full during subsequent searches.”
If we can efficiently determine “when champagne flows into glass \(i\), what is the first glass from glass \(i\) onward that is not yet full (has remaining capacity)?”, we can eliminate unnecessary scanning.
This can be solved in nearly constant time using Union-Find (with path compression).
Specifically, we manage groups for each glass \(i\) as follows:
- Let parent[i] be “the index (representative) of the smallest glass from glass \(i\) onward that still has remaining capacity.”
- In the initial state, all glasses have remaining capacity, so parent[i] = i.
- The moment glass \(i\) becomes full, we merge glass \(i\)’s parent to “the representative of glass \(i+1\)’s group.”
This way, by simply calling find(i), we can instantly find the next glass that should receive champagne from glass \(i\) onward.
Algorithm
1. Data Preparation
- Array \(C\) storing the capacity of each glass
- Array
amountstoring the current champagne level of each glass (initially all0) - Array
parentfor Union-Find- Since we use 1-indexed management, the size is \(N+2\).
- The \((N+1)\)-th element serves as a “sentinel (overflow tray)” for when all glasses are full and champagne overflows from the tower.
2. Processing Operation \(1\) (Pouring)
Pour \(V\) milliliters of champagne into the 1st glass.
- Obtain the topmost “non-full glass” with
curr = find(1). - While \(V > 0\) and
curr <= N(within the tower’s range), repeat the following:- Calculate the remaining capacity of glass
curr:space = C[curr] - amount[curr]. - Case A: \(V \ge space\) (the glass becomes full)
- Set glass
curr’s champagne amount to full (C[curr]). - Update the pouring amount: \(V \leftarrow V - space\).
- Since glass
curris now full, merge its parent to the adjacent glass’s group:parent[curr] = find(curr + 1). - Update the next pouring destination:
curr = find(curr + 1).
- Set glass
- Case B: \(V < space\) (the glass does not become full)
- Pour all remaining champagne into glass
curr:amount[curr] += V. - Set \(V = 0\) since there is no more champagne to pour, and exit the loop.
- Pour all remaining champagne into glass
- Calculate the remaining capacity of glass
3. Processing Operation \(2\) (Query)
- Simply output the current amount of champagne in the \(k\)-th glass:
amount[k].
Complexity
Time Complexity: \(O((N + Q) \alpha(N))\)
- The event of “a glass becoming full” occurs at most once per glass across the entire tower.
- Therefore, the number of Union-Find merges (parent updates) across all queries is at most \(N\).
- The Union-Find
findoperation, with path compression, runs in \(O(\alpha(N))\) (nearly constant time), where \(\alpha\) is the inverse Ackermann function. - Thus, the overall time complexity is \(O((N + Q) \alpha(N))\), which comfortably fits within the time limit.
Space Complexity: \(O(N)\)
- The capacity array \(C\), current amount array
amount, and Union-Findparentarray are all of size \(O(N)\). - Therefore, the space complexity is \(O(N)\).
Implementation Notes
Introduction of a Sentinel (Level \(N+1\)): When the bottom glass at level \(N\) becomes full, we merge it to point to
find(N + 1). This prevents out-of-bounds array access (Index Error) while also cleanly simulating the specification that “overflowed champagne is lost and does not accumulate anywhere.”Fast I/O in Python: Since Python has significant I/O overhead, we read all input at once using
sys.stdin.read().split()and output usingsys.stdout.writeto batch the output, significantly reducing execution time.Source Code
import sys
def solve():
# 入力をすべて取得
input_data = sys.stdin.read().split()
if not input_data:
return
N = int(input_data[0])
Q = int(input_data[1])
# 1-indexedにするため、サイズは N + 2 とする
C = [0] * (N + 2)
for i in range(1, N + 1):
C[i] = int(input_data[1 + i])
# Union-Find(Disjoint Set Union)の初期化
# parent[i] は、グラス i 以降でまだ満杯になっていない(空き容量がある)最小のグラスのインデックスを指す
parent = list(range(N + 2))
amount = [0] * (N + 2)
def find(i):
curr = i
while parent[curr] != curr:
curr = parent[curr]
# 経路圧縮
p = i
while p != curr:
nxt = parent[p]
parent[p] = curr
p = nxt
return curr
idx = N + 2
out = []
len_data = len(input_data)
while idx < len_data:
type_q = int(input_data[idx])
if type_q == 1:
V = int(input_data[idx + 1])
idx += 2
curr = find(1)
while curr <= N and V > 0:
space = C[curr] - amount[curr]
if V >= space:
# グラス curr が満杯になる
amount[curr] = C[curr]
V -= space
# 満杯になったので、次の空いているグラスの代表元にマージする
nxt = find(curr + 1)
parent[curr] = nxt
curr = nxt
else:
# グラス curr は満杯にならない
amount[curr] += V
V = 0
else:
k = int(input_data[idx + 1])
idx += 2
out.append(str(amount[k]))
# まとめて出力
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
solve()
This editorial was generated by gemini-3.5-flash-thinking.
posted:
last update: