E - 会社の組織と給与 / Company Organization and Salaries Editorial by admin
Claude 4.6 Opus (Thinking)Overview
This problem involves efficiently processing queries on a company organization represented as a rooted tree: updating employee salaries and counting the number of subordinates of a given employee whose salary is higher than that employee’s salary.
Approach
Converting Subtrees to Intervals
First, to efficiently handle “all subordinates of employee \(v\)”, we want to convert the tree structure into contiguous intervals on an array. This can be achieved using an Euler Tour.
By arranging employees in DFS visit order, the employees belonging to the subtree of employee \(v\) correspond to a contiguous interval \([\mathrm{tin}[v], \mathrm{tout}[v]]\) on the array. The subordinates (excluding \(v\) itself) correspond to the interval \([\mathrm{tin}[v]+1, \mathrm{tout}[v]]\).
Issues with the Naive Approach
If we scan all subordinates for each query 2, it takes \(O(N)\) in the worst case, resulting in \(O(NQ)\) for \(Q\) queries. When \(N, Q \leq 10^5\), this amounts to up to \(10^{10}\) operations, which will TLE.
Solving with Sqrt Decomposition
Since the problem can be reduced to counting the number of elements greater than a given value in an interval on an array, we apply Sqrt Decomposition.
We divide the array into blocks of size \(B \approx \sqrt{N}\) and also maintain a sorted list of elements within each block. This allows:
- Update: Remove the old value from the corresponding block’s sorted list and insert the new value → \(O(B)\)
- Range query: For fully contained blocks, use binary search in \(O(\log B)\); for partial blocks at the endpoints, use linear scan in \(O(B)\) → Overall \(O(\frac{N}{B} \log B + B)\)
Setting \(B = \sqrt{N}\), each query can be processed in approximately \(O(\sqrt{N} \log N)\).
Algorithm
Euler Tour Construction: Perform an iterative DFS rooted at employee \(1\), recording the entry time \(\mathrm{tin}[v]\) and exit time \(\mathrm{tout}[v]\) for each employee. Create an array
euler_salarywith salaries arranged in DFS order.Sqrt Decomposition Initialization: Divide
euler_salaryinto blocks of size \(B = 250\) or so, and create a sorted listblocks[b]of the values within each block.Query Processing:
- Update query
1 v x: Find the position of employee \(v\) in the Euler Tour, update the value ineuler_salary. Remove the old value from the corresponding block’s sorted list and insert the new value. - Investigation query
2 v: For the subordinate interval \([l, r] = [\mathrm{tin}[v]+1, \mathrm{tout}[v]]\), count the number of elements strictly greater than \(A[v]\). For complete blocks, usebisect_rightfor binary search; for partial blocks at the endpoints, use linear scan.
- Update query
Complexity
- Time complexity: \(O(N \log N + Q \cdot (\sqrt{N} \log N + \sqrt{N}))\) (each query is approximately \(O(\sqrt{N} \log N)\); updates are \(O(\sqrt{N})\))
- Space complexity: \(O(N)\)
Implementation Notes
Implementing Euler Tour with iterative DFS: For \(N \leq 10^5\), recursive DFS in Python hits the stack limit, so we use an explicit stack for iterative processing.
Updating the sorted list: Find the position of the old value using
bisect_right(block, old_val - 1)andpopit, then useinsortto insert the new value. This allows updates in \(O(B)\).Case when there are 0 subordinates: When \(l > r\), immediately output
0(e.g., for leaf nodes).Block size selection: \(B = 250\) or so works well in practice. Since Python has large constant factors, it is important to use binary search for complete blocks to achieve speedup.
Source Code
import sys
from bisect import bisect_right, insort
def main():
input_data = sys.stdin.buffer.read().split()
idx = 0
N = int(input_data[idx]); idx += 1
Q = int(input_data[idx]); idx += 1
A = [0] * (N + 1)
for i in range(1, N + 1):
A[i] = int(input_data[idx]); idx += 1
children = [[] for _ in range(N + 1)]
for i in range(2, N + 1):
p = int(input_data[idx]); idx += 1
children[p].append(i)
# Euler tour to get subtree ranges
tin = [0] * (N + 1)
tout = [0] * (N + 1)
order = [] # euler tour order
# Iterative DFS for Euler tour
timer = 0
stack = [(1, False)]
while stack:
node, visited = stack.pop()
if visited:
tout[node] = timer - 1
else:
tin[node] = timer
timer += 1
order.append(node)
stack.append((node, True))
for c in reversed(children[node]):
stack.append((c, False))
# For N, Q <= 5*10^4, we can use sqrt decomposition
# Block size for sqrt decomposition on the Euler tour array
# Each block maintains a sorted list of salary values
BLOCK = 250 # sqrt(50000) ~ 224, use 250
num_blocks = (N + BLOCK - 1) // BLOCK
# euler[i] = salary of the node at position i in euler tour
euler_salary = [0] * N
euler_node = [0] * N # which node is at position i
node_pos = [0] * (N + 1) # position of node in euler tour
for i in range(N):
node = order[i]
euler_salary[i] = A[node]
euler_node[i] = node
node_pos[node] = i
# Build sorted blocks
blocks = []
for b in range(num_blocks):
start = b * BLOCK
end = min(start + BLOCK, N)
sorted_block = sorted(euler_salary[start:end])
blocks.append(sorted_block)
def update(node, new_val):
pos = node_pos[node]
old_val = euler_salary[pos]
euler_salary[pos] = new_val
A[node] = new_val
b = pos // BLOCK
# Remove old_val and insert new_val in sorted block
block = blocks[b]
# Find and remove old_val
lo = bisect_right(block, old_val - 1)
# block[lo] should be old_val
# Use index to find exact position
block.pop(lo)
insort(block, new_val)
def count_greater(l, r, val):
# Count elements strictly greater than val in euler_salary[l..r]
if l > r:
return 0
result = 0
lb = l // BLOCK
rb = r // BLOCK
if lb == rb:
# Same block, brute force
for i in range(l, r + 1):
if euler_salary[i] > val:
result += 1
return result
# Left partial block
left_end = (lb + 1) * BLOCK - 1
for i in range(l, left_end + 1):
if euler_salary[i] > val:
result += 1
# Full blocks
for b in range(lb + 1, rb):
block = blocks[b]
# Count elements > val
pos = bisect_right(block, val)
result += len(block) - pos
# Right partial block
right_start = rb * BLOCK
for i in range(right_start, r + 1):
if euler_salary[i] > val:
result += 1
return result
out = []
for _ in range(Q):
t = int(input_data[idx]); idx += 1
if t == 1:
v = int(input_data[idx]); idx += 1
x = int(input_data[idx]); idx += 1
update(v, x)
else:
v = int(input_data[idx]); idx += 1
# Subtree of v: positions tin[v] to tout[v]
# Descendants only: tin[v]+1 to tout[v]
l = tin[v] + 1
r = tout[v]
if l > r:
out.append('0')
else:
val = A[v]
ans = count_greater(l, r, val)
out.append(str(ans))
sys.stdout.write('\n'.join(out) + '\n')
main()
This editorial was generated by claude4.6opus-thinking.
posted:
last update: