E - 倉庫の在庫管理 / Warehouse Inventory Management Editorial by admin
gpt-5.5-highOverview
Let \(D_i = B_i - A_i\). The value we want to find is \(\sum_i \max(0, D_i)\).
Since each report is an operation that adds to or subtracts from \(D_i\) over an interval, we will efficiently manage “interval addition” and “sum of positive elements”.
Analysis
First, for each warehouse, what is important is not \(A_i\) and \(B_i\) themselves, but their difference:
\(D_i = B_i - A_i\)
Since the shortage is \(\max(0, D_i)\), the answer is:
\(\sum_{i=1}^{N} \max(0, D_i)\)
The change caused by each report can be rephrased as follows:
- \(T_j = 1\): \(B_i\) increases by \(X_j\)
→ \(D_i\) increases by \(X_j\) - \(T_j = 2\): \(A_i\) increases by \(X_j\)
→ \(D_i\) decreases by \(X_j\)
In other words, the problem reduces to the following:
Perform interval additions on an array \(D\), and find \(\sum \max(0, D_i)\) after each query.
Naively updating all elements in the interval for each query and then summing them up would take \(O(NQ)\) time.
Since \(N, Q \leq 5 \times 10^4\), this would require up to around \(2.5 \times 10^9\) operations, which is too slow to pass within the time limit.
Therefore, we use square root decomposition.
We divide the array into several blocks and process updates to entire blocks in bulk.
By maintaining a sorted array of elements and their prefix sums for each block, we can quickly recalculate:
\(\sum \max(0, D_i)\)
Algorithm
We maintain \(D_i = B_i - A_i\) as an array.
We divide the array into blocks of size approximately \(K\).
In our implementation, we set \(K = 256\).
For each block, we maintain the following information:
arr[i]: The value of each elementlazy[b]: The lazy addition value applied to the entire blocksblocks[b]: The sorted version ofarrwithin the blockprefs[b]: The prefix sums ofsblocks[b]bsum[b]: The sum \(\sum \max(0, D_i)\) for this blocktotal: The sum ofbsumover all blocks, which is the current answer
Here, the actual value of an element is:
\(arr[i] + lazy[b]\)
Updating an Entire Block
Consider the case where we add \(\delta\) to an entire block.
- If \(T = 1\), then \(\delta = X\)
- If \(T = 2\), then \(\delta = -X\)
Since this is an addition to the entire block, we do not modify arr[i] directly; instead, we add \(\delta\) to lazy[b].
After that, we calculate:
\(\sum \max(0, arr[i] + lazy[b])\)
for that block.
Let \(v\) be the sorted array of values in the block.
The condition for a value to be positive is:
\(v + lazy[b] > 0\)
which is:
\(v > -lazy[b]\)
We perform a binary search on the sorted array to find the last position where \(v \leq -lazy[b]\).
Only the elements after this position will be positive.
If the number of positive elements is \(cnt\) and the sum of their arr values is \(sum\), the total shortage for the block is:
\(sum + lazy[b] \times cnt\)
Since we maintain prefix sums, \(sum\) can be obtained in \(O(1)\) time, and the boundary position can be found in \(O(\log K)\) time using binary search.
Updating a Part of a Block
When only a part of a block is to be updated, such as at the ends of the interval, we directly update those elements.
For each element, let the actual value before the update be:
\(old = arr[i] + lazy[b]\)
and the value after the update be:
\(new = old + \delta\)
The change in the answer is:
\(\max(0, new) - \max(0, old)\)
We reflect this change in bsum[b] and total.
We also increase arr[i] by \(\delta\).
However, this operation makes the sorted array sblocks[b] in the block outdated.
Therefore, we set a dirty flag for that block.
Before using that block again in an “entire block update”, we rebuild the sorted array and prefix sums if necessary.
Query Processing
For each query on the interval \([L, R]\), we process it as follows:
- If \(T = 1\), let \(\delta = X\); if \(T = 2\), let \(\delta = -X\).
- Identify which blocks are covered by the interval.
- For the partially covered blocks at the left and right ends, update the elements directly one by one.
- For the fully covered blocks, update them in bulk using lazy addition and binary search.
- Output
total.
Complexity
Let \(K\) be the block size.
- Updating partial blocks takes \(O(K)\) time since there are at most 2 such blocks.
- There are at most \(O(N/K)\) fully covered blocks, and each takes \(O(\log K)\) time for binary search.
- Rebuilding a
dirtyblock takes \(O(K \log K)\) time, but since this is only done when necessary for blocks that had partial updates, it can be amortized.
Therefore, the amortized time complexity per query is approximately:
\(O\left(K \log K + \frac{N}{K}\log K\right)\)
If we set \(K \approx \sqrt{N}\):
- Time Complexity: around \(O(Q \sqrt{N} \log N)\)
- Space Complexity: \(O(N)\)
In our implementation, we use \(K = 256\), which is sufficiently fast for \(N, Q \leq 5 \times 10^4\).
Key Implementation Points
We treat arr[i] not as the actual value itself, but as the value excluding the block’s lazy offset.
The actual value is always:
\(arr[i] + lazy[b]\)
In partial updates, instead of applying lazy before updating, we directly add \(\delta\) to arr[i].
This correctly changes the actual value by \(\delta\).
Also, since we only sum the positive parts, we use binary search to find the first position where:
\(v + lazy > 0\)
which is:
\(v > -lazy\)
Since elements that are exactly \(0\) do not contribute to the shortage, we use bisect_right.
The answer can be up to \(10^{18}\), so in other languages, you must use a 64-bit integer type. In Python, integers have arbitrary precision, so you do not need to worry about overflow.
Source Code
import sys
from bisect import bisect_right
def main():
data = list(map(int, sys.stdin.buffer.read().split()))
if not data:
return
N = data[0]
Q = data[1]
pos = 2
arr = [0] * N
for i in range(N):
A = data[pos]
B = data[pos + 1]
pos += 2
arr[i] = B - A
SHIFT = 8
BS = 1 << SHIFT
nb = (N + BS - 1) >> SHIFT
ends = [0] * nb
lens = [0] * nb
sblocks = [None] * nb
prefs = [None] * nb
base_sum = [0] * nb
lazy = [0] * nb
bsum = [0] * nb
dirty = [0] * nb
brgt = bisect_right
total = 0
for b in range(nb):
s = b << SHIFT
e = s + BS
if e > N:
e = N
ends[b] = e
m = e - s
lens[b] = m
lst = sorted(arr[s:e])
sblocks[b] = lst
pref = [0] * (m + 1)
acc = 0
j = 1
for v in lst:
acc += v
pref[j] = acc
j += 1
prefs[b] = pref
base_sum[b] = acc
idx = brgt(lst, 0)
sm = acc - pref[idx]
bsum[b] = sm
total += sm
def rebuild(b, a=arr, ends=ends, lens=lens, sblocks=sblocks,
prefs=prefs, base_sum=base_sum, dirty=dirty, shift=SHIFT):
s = b << shift
e = ends[b]
m = lens[b]
lst = sorted(a[s:e])
sblocks[b] = lst
pref = [0] * (m + 1)
acc = 0
j = 1
for v in lst:
acc += v
pref[j] = acc
j += 1
prefs[b] = pref
base_sum[b] = acc
dirty[b] = 0
def partial_update(b, left, right, delta, total,
a=arr, lazy=lazy, bsum=bsum, dirty=dirty):
lz = lazy[b]
tb = bsum[b]
if delta > 0:
for i in range(left, right + 1):
base = a[i]
old = base + lz
a[i] = base + delta
if old > 0:
tb += delta
total += delta
else:
nv = old + delta
if nv > 0:
tb += nv
total += nv
else:
for i in range(left, right + 1):
base = a[i]
old = base + lz
a[i] = base + delta
if old > 0:
nv = old + delta
if nv > 0:
tb += delta
total += delta
else:
tb -= old
total -= old
bsum[b] = tb
dirty[b] = 1
return total
end_l = ends
lazy_l = lazy
bsum_l = bsum
sblocks_l = sblocks
prefs_l = prefs
lens_l = lens
dirty_l = dirty
base_sum_l = base_sum
rebuild_func = rebuild
partial_func = partial_update
out = []
append = out.append
for _ in range(Q):
T = data[pos]
l = data[pos + 1] - 1
r = data[pos + 2] - 1
X = data[pos + 3]
pos += 4
delta = X if T == 1 else -X
bl = l >> SHIFT
br = r >> SHIFT
if bl == br:
if l == (bl << SHIFT) and r == end_l[bl] - 1:
fb = bl
lb = bl
else:
total = partial_func(bl, l, r, delta, total)
append(str(total))
continue
else:
fb = bl
lb = br
if l != (bl << SHIFT):
total = partial_func(bl, l, end_l[bl] - 1, delta, total)
fb = bl + 1
if r != end_l[br] - 1:
total = partial_func(br, br << SHIFT, r, delta, total)
lb = br - 1
for b in range(fb, lb + 1):
if dirty_l[b]:
rebuild_func(b)
old_sum = bsum_l[b]
nlz = lazy_l[b] + delta
lazy_l[b] = nlz
sv = sblocks_l[b]
m = lens_l[b]
if sv[0] + nlz >= 0:
new_sum = base_sum_l[b] + nlz * m
elif sv[-1] + nlz <= 0:
new_sum = 0
else:
idx = brgt(sv, -nlz)
new_sum = base_sum_l[b] - prefs_l[b][idx] + nlz * (m - idx)
bsum_l[b] = new_sum
total += new_sum - old_sum
append(str(total))
sys.stdout.write("\n".join(out))
if __name__ == "__main__":
main()
This editorial was generated by gpt-5.5-high.
posted:
last update: