1 of 110

2022 NUS Dec Course BAPS Editorial

2 of 110

Finding Grass

omg is this a touching grass reference?!?!?

Also please read the full story

3 of 110

Abridged Statement

There are two arrays A and B of length N, as well as Q updates, each changing the value of exactly one element.

Before the first update, and after every update, find any index 1kn such that:

4 of 110

Subtask 2: N ≤ 2000, Q ≤ 50

Just brute force.

For each answer, just try all possible indices k and check if it’s possible in O(N).

Time complexity: O(QN2)

5 of 110

Subtask 3: N ≤ 20 000, Q ≤ 50

Better brute force.

For each k, how to check if it’s possible in log time?

(aka how does the checker work?)

6 of 110

Subtask 3: N ≤ 20 000, Q ≤ 50

Use a segment tree.

Each node stores the sum of elements and number of elements with value in its range.

For each ak, to calculate , split into aiak and ai > ak.

Sum for aiak: ak*root->number(-INF, ak) - root->sum(-INF, ak)

Sum for ai > ak: root->sum(ak+1, INF) - ak*root->number(ak+1, INF)

This can be done similarly to calculate . (Use another segment tree.)

Check for all k, time complexity O(NQ log A) where A is the range of values of a and b.

7 of 110

Subtask 3: N ≤ 20 000, Q ≤ 50

Apparently you can scam with subtask 2 solution by only finding a new index if the previous index fails.

I tried to kill; my code runs in >15s on polygon but only takes 2s on codebreaker for some reason.

8 of 110

Subtask 4: N ≤ 20 000, Q ≤ 10 000

Hmm.. how to O(NQ)?

For each k, how to check if it’s possible in O(1) time?

9 of 110

Subtask 4: N ≤ 20 000, Q ≤ 10 000

Hmm.. how to O(NQ)?

For each k, how to check if it’s possible in O(1) time?

Idk, instead let’s make an observation!

10 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

I initially wanted to add this subtask but thought it might be too revealing of the full solution.

Draw the points on a number line. (Assume a, b are sorted.)

a

b

x

11 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

What is ?

It is the sum of distances from ak to all other ai!

a

b

12 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

What is ?

It is the sum of distances from ak to all other ai!

k = 1:

a

b

13 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

What is ?

It is the sum of distances from ak to all other ai!

k = N:

a

b

14 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

What is ?

It is the sum of distances from ak to all other ai!

Add k = 1 and k = N: Hmmm… isn’t this just n(an - a1)?

a

b

15 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

What is ?

It is the sum of distances from ak to all other ai!

Add k = 1 and k = N: Hmmm… isn’t this just n(an - a1)?

a

b

What about ?

16 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

What is ?

It is the sum of distances from ak to all other ai!

Add k = 1 and k = N: Hmmm… isn’t this just n(an - a1)?

a

b

What about ?

17 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

What is ?

It is the sum of distances from ak to all other ai!

Add k = 1 and k = N: Hmmm… isn’t this just n(an - a1)?

a

b

What about ?

Hmmm…

This is also n(an - a1)!

18 of 110

What if min(ai) ≤ min(bi), max(ai) ≥ max(bi)?

What is ?

It is the sum of distances from ak to all other ai!

Add k = 1 and k = N: Hmmm… isn’t this just n(an - a1)?

What about ?

Hmmm…

This is also n(an - a1)!

Since adding up k = 1 and k = N makes both sides equal, at least one of k = 1 and k = N is a solution!

19 of 110

The general case

What if the range of b is out of the range of a?

20 of 110

The general case

What if the range of b is out of the range of a?

a

b

21 of 110

The general case

What if the range of b is out of the range of a?

increases, but stays the same.

a

b

22 of 110

Observation 1

What if the range of b is out of the range of a?

increases, but stays the same.

Thus by adding k = 1 and k = N, we get LHS ≤ RHS.

So at least one of k = 1 and k = N is a solution!

23 of 110

Subtask 4: N ≤ 20 000, Q ≤ 10 000

Check min and max element of a in O(N) time for each answer, output the one which satisfies the inequality.

Time complexity: O(NQ)

24 of 110

Subtask 5: 1 ≤ ai, bi, vi ≤ 200 000

This is for full solutions which did not use lazy node creation for segment trees.

25 of 110

Subtask 6: No additional constraints (Solution 1)

Combine solutions for subtask 3 and 4:

For each answer, check min and max element of a in O(log A) using a segment tree.

Time complexity: O((N+Q) log A) where A is the range of values of a and b.

26 of 110

Subtask 6: No additional constraints (Solution 2)

(did anyone actually read this…?)

But where is the magical solution???

27 of 110

Subtask 6: No additional constraints (Solution 2)

Instead of finding an ak which satisfies the inequality, let’s try finding any integer x such that

28 of 110

Subtask 6: No additional constraints (Solution 2)

Instead of finding an ak which satisfies the inequality, let’s try finding any integer x such that

a

b

x

29 of 110

Subtask 6: No additional constraints (Solution 2)

Instead of finding an ak which satisfies the inequality, let’s try finding any integer x such that

a

b

x

x'

30 of 110

Subtask 6: No additional constraints (Solution 2)

Instead of finding an ak which satisfies the inequality, let’s try finding any integer x such that

a

b

x

x'

x''

31 of 110

Subtask 6: No additional constraints (Solution 2)

Instead of finding an ak which satisfies the inequality, let’s try finding any integer x such that

a

b

x

x'

x''

As x increases from max(ai) to infinity, LHS increases at least as much as RHS

32 of 110

Observation 2

Instead of finding an ak which satisfies the inequality, let’s try finding any integer x such that

As x increases from max(ai) to infinity, LHS increases at least as much as RHS.

If x = infinity works, then x = max(ai) also works. (Note: the converse is not true.)

Similarly, if x = -infinity works, then x = min(ai) also works.

Furthermore, adding up both sides for x = infinity and x = -infinity, both sides are equal to 2n*infinity (you can do the math).

Therefore LHS ≤ RHS for at least one of x = infinity and x = -infinity.

33 of 110

Subtask 6: No additional constraints (Solution 2)

Now suppose x = -infinity works. Then means

(a1+INF) + (a2+INF) + … + (an+INF) ≤ (b1+INF) + (b2+INF) + … + (bn+INF)

a1 + a2 + … + anb1 + b2 + … + bn

Note that the converse is true as well.

34 of 110

Subtask 6: No additional constraints (Solution 2)

For each answer,

  • If sum(ai) ≤ sum(bi), output the index of the min element of a.
  • Otherwise, output the index of the max element of a.

Time complexity: O((N+Q) log N)

35 of 110

Removal Costs

Div 2C - shiwei

36 of 110

Not Abridged Statement

37 of 110

Observation 1a

For all positions that have ai ≥ i, the minimum possible contribution to cost is ai - i as doing the operations can only decrease i.

On the other hand, for all positions that have ai < i, the minimum possible contribution to cost is 0 as we can do the operations to decrease i until ai = i before doing an operation on i.

It is actually possible to achieve the minimum for both cases.

38 of 110

Observation 1b

Algorithm to achieve minimum:

  1. Find the largest position i such that ai ≥ i and do operation on it.
  2. Update the positions of all elements after position i.
  3. Go back to step 1 until array is empty.

Since we do the operation on the largest position with ai ≥ i, we do not affect the cost of all other positions j with aj ≥ j, and at the same time reducing the costs of all j > i since aj < j. We only do operations on them the moment aj = j due to step 1, hence this is the most optimal.

39 of 110

Observation 2

With observation 1a, we discover that each element is independent of the rest, hence we can consider the contribution of each element individually.

40 of 110

Full solution

Let x be the number of -1 and S be the set of elements not in the permutation yet.

For each position i, if ai ≠ -1, then cost is max(ai - i, 0) * x!

Otherwise, cost = Σv ∈ S max(v - i, 0) * (x - 1)!

To calculate the cost for the second case quickly, we can use sliding window to take note of which term of the max is used as i increases.

41 of 110

Reopening Shop

When you thought the opening shop plugs could not get worse

(thx errorgorn for q)

42 of 110

Abridged Statement

Nodes 1 to N form a tree, and you have node 0 connecting to each of them

When you select an edge (u,v) you will update

d[v] = min(d[v], d[u] + t_i) for some positive unknown t_i

Find a sequence of edges such that d[u] = shortest distance from 0 to u for all nodes u

43 of 110

Observation 0

Always take the roads going out from 0 first.

Your sequence should always start with

0 x_i�0 x_j�0 x_k�0 x_l�0 x_m

44 of 110

Subtask 1 (M <= 2)

You just dfs/bfs from the 2 points to the entire tree

Why does this work?

M = 1: DFS from 1 point

M = 2: DFS from first point, then second point (d[u] = min(d[first point dfs], d[second point dfs]

45 of 110

Subtask 2 (tree = line)

You just go along the line

1

2

3

4

5

6

7

0

46 of 110

Subtask 2 (tree = line)

And back

1

2

3

4

5

6

7

0

47 of 110

Subtask 2

Why does this work?

There are only at most 3 cases for a node (5 in prev example)

Either from left, right or 0

0 alr considered (Observation 0)

From 4 is the first pass (min path to 4 from left)

From 6 is the reverse pass (min path to 6 from right)

4

5

6

0

48 of 110

Subtask 3 (Full Solution)

Here is a 🌳🌳tree🌳🌳

From observation 0, you see that we can just ignore 0

1

2

3

4

5

6

7

49 of 110

Subtask 3 (Full Solution)

A shortest path only has 3 cases

1

2

3

4

5

6

7

50 of 110

Subtask 3 (Full Solution)

Up the Tree

This is simple, just dfs�Go from cur to par in post-order

Why not pre-order?

Then the path 5-2-1 breaks at 2 (as 1 updated before 2)

1

2

3

4

5

6

7

51 of 110

Subtask 3 (Full Solution)

Down the Tree

Also simple, just dfs�Go from cur to child in pre-order

Why not post-order?

Then the path 1-2-5 breaks at 2 (as 5 updated before 2)

1

2

3

4

5

6

7

52 of 110

Subtask 3 (Full Solution)

Up then down a tree

If we used the up the tree case then down the tree�It would work!

This is because after handling up the tree�d[1] = min(path from parent, path from child, 0 to 1)�And then going down the tree will just update d[4] according to min dist of 1

1

2

3

4

5

6

7

53 of 110

Subtask 3 (Full Solution)

2 DFS solves problem, yay

Time complexity O(V + E)�Wow so fast!!!!

(Note that you can arbitrarily root the tree)

1

2

3

4

5

6

7

54 of 110

Valves

A valve is a device or natural object that regulates, directs or controls the flow of a fluid (gases, liquids, fluidized solids, or slurries) by opening, closing, or partially obstructing various passageways. -Wikipedia

55 of 110

Abridged Statement

Range Add Range Sum but support “valves” and these two additional queries.

Initially, the only open valve is at infinity (implicitly)

Open i h: Insert h into the open valves of i

Close i h: Erase h into the open valves of i

Let x_i be the lowest open valve of i. Then, after each operation, sweep from 1 to N-1 (tower N will not overflow),

If val[i]>x_i, val[i+1]+=val[i]-x_i, val[i]=x_i.

56 of 110

Observation 0:

Only the lowest open valve matters

Maintain a set for all open valves for all towers

Remember this for all the future solutions

57 of 110

Subtask 2: Queries will only be on tower 1

Only care about operations if they involve 1

Maintain a set of the open valves for tower 1 only

When pour, answer = max(answer+X, open)

When open and close, update set accordingly

answer=min(answer, lowest_open)

When query, just output answer

58 of 110

Subtask 3: No valve operations

Literally Range Add Range Sum Segtree lol

59 of 110

Subtask 4: N,Q <= 3000

Just simulate

Keep a counter of overflow

Maintain a set of all open valves, when pour just check accordingly.

If open a lower valve, rmbr to overflow into next valve

Query just add them up

O(NQ)

60 of 110

Subtask 5: N,Q <= 100 000

Intended for SQRT solutions

But the SQRT solution uses similar tricks to full solution.

Tricks explained later

61 of 110

Subtask 6: Point Update Point Sum

Actually, the brute force solution passes this subtask 🤡

Might have accidentally gotten this subtask

Notice that we can only overflow at most Q times

So amortised O(Q) overflows

Time complexity O(Qlog(Q))

62 of 110

Subtask 7: Point Update Range Query

Maintain 2 segtrees, 1 is for the lowest open valves

In the other valve, store height-valve, call this segtree deficit.

When pour at L, check if it overflows.

If it does, do a binary search to find the last position it will fill.

Range set all between the L to last pos filled to 0. Then update the non-overflow thing appropriately

63 of 110

Subtask 7: Point Update Range Query

On open/close, check if it affects the lowest valve, then update accordingly

When query, take Valves(L,R)-Deficit(L,R) and take the mod.

Remember not to mod until right before outputting the answer. If take mod, then deficit will be inaccurate and cannot be updated correctly

Anyway, total amt of water is 1e5*1e8=1e13, so it will definitely not overflow

64 of 110

Subtask 8: Range Update Point Query

The main issue here is: “How can we handle many overflow fast?”

If we simply check all the overflow, it will be too slow.

We need a faster idea.

65 of 110

Observation 1:

Water can only go from underflow to overflow Q times.

Make use of that amortisation

You may want to keep an array that keeps track of whether the tower will overflow

66 of 110

Observation 2:

Consider towers 1,2,3,4 are filled and tower 5 is not. Then when we pour 1-5 with 4, we are really adding 20m to tower 5.

But what if tower 3 is unfilled? Then we pour 12m into tower 3 and 8m into tower 5.

So, we realise that when something overflows, we can just dump it into the next guy that is not overflowing.

Then when we add, we can add length of segment* amount.

But how do we add unequal segment lengths?

Magic: we store deficit as deficit/length instead.

67 of 110

Observation 2:

Assuming we cover the whole length of segment

Added value is just (length of segment)*amt.

We can keep track of the length of segment with a map.

We invert deficit to now store valve-height.

To detect when we overflow, we store it inside a RMQ segtree, when the largest value is >=0, then it means something has overflowed

Now, we store (valve-height)/length instead, inside a RMQ segtree

When we range add, we just add it to this new value.

68 of 110

Subtask 8: Range Update Point Query

Pour L R X:

Find the first point where length is not 0 for both L and R. Then update the whole range by adding X.

Of course, it might not cover the whole range.

So compensate by “refunding” the segments they do not cover.

When refunding, remember that it is refunding val/len and not val itself

69 of 110

Subtask 8: Range Update Point Query

Open i h:

If it is not the new lowest, can just push into set and ignore.

Else, update the valve height and the deficit accordingly

70 of 110

Subtask 8: Range Update Point Query

Closing is similar,

If it is not the lowest, just ignore

If it is, update lowest valve and deficit accordingly

71 of 110

Subtask 8: Range Update Point Query

Query:

Find the valve height + deficit*length (to reverse what we divided by) then output it

If it is overflowing as indicated by array, just output valve height.

72 of 110

Subtask 8: Range Add Point Sum

After every operation, we check if the largest value in the RMQ >=0. If it is, then it means we have overflowed. Find the next point where it has not overflowed, and pour it into there, by adding (overflow/length)

Then, set the value to -inf, to ensure it never becomes positive again, and set length=1.

73 of 110

Implementation Detail

Doubles are able to pass this question, but there is an implementation with fractions (which is the model code)

Remember to take mod only on the valve segtree.

Check maomao90 submissions to valves should be one of them

74 of 110

Subtask 9: No closing operations

Idk lmao

It’s mostly a debugging subtask

75 of 110

A Subproblem

Consider a subproblem

You have arrays A and B, support the following operations:

Add L R X => A[i]+=B[i]*X for all L<= i <= R

Update X V => B[X]= V

Query L R => find the sum of all A[i] for L<=i<=R

76 of 110

A Subproblem

Store two segment trees, for A and B.

When add, do the lazy flag same as normal segment tree.

When adding to A, add B->query(s,e)*flag instead;

When updating a value of B, remember to first push down all updates on A containing the value, so that we do not mess things up.

Query remains similar.

77 of 110

Subtask 10: Full Solution

How does this help us with the full solution?

In subtask 8, we could only come up with query at a point as we could not do sum on fractions.

In subtask 7, we could only update at a point as we could not do addition on the various lengths.

How about we combine them?

78 of 110

Subtask 10: Full Solution

Store 4 segtrees.

Valves segtree: Keeps track of the lowest open valve for all 1 to N, and can take sum easily.

RMQ segtree: Same as subtask 8, store it as deficit/length

Length segtree: In a similar idea to our subproblem, we store the lengths of the “pouring segment”. This is our array B from the subproblem.

Sum segtree: This is our array A from the subproblem, here we store actual deficit

Then, for the four operations, do these actions accordingly

79 of 110

Subtask 10: Full Solution

Initially, set all valves to be at inf, all values in sum segtree to be -inf (%mod), RMQ segtree to be -inf, len=1, and length segtree to be all 1

Also, set inf to be 3e18, as it is a value we will never attain.

At worst, open half, pour half = Q/2*Q/2*X=2.5x10^17

80 of 110

Subtask 10: Full Solution

Pour L R X: Update RMQ segtree with X and sum segtree with X.

Open i h: Update the set, and check if it is new lowest. If it is, update accordingly

Close i h: Update the set, and check if the closed one was the lowest. If it was, update accordingly

Query L R: Do valves(L,R)-actual deficit(L,R)

81 of 110

Subtask 10: Full Solution

Then, after every operation, we do the following

Query RMQ segtree to see if highest value >=0.

If it is, we join it to the next available non-overflow (which can be kept track of using a map), pouring the overflow into the next one.

Update the value in Length Segtree (remember to propo all updates on sum segtree first)

And update the length in the RMQ segtree to reflect the length changes.

82 of 110

Implementation Detail

You may have noticed that the values are really large. Actually, we only need to take mod on the valves segtree and the sum segtree. If we mod the other two, it will be incorrect. (you cannot mod RMQ)

83 of 110

Subtask 10: Full Solution

Since there can only be at most Q overflows, we will only do it Q times, each time taking log(N) time.

Each operation is on a segment tree/set, so takes at most log(N+Q) time ( or logQ if you do some lazy tricks)

In total, time complexity is O((N+Q)log(N+Q))

84 of 110

Subtask 10: Full Solution

Actually you can combine the RMQ and Sum segtree into 1.

Author AC Code is about 250 lines long.

Contact __sheep on discord (if you have handle) for any queries/clarification/help/request for code/feedback

85 of 110

Subtask 10: Summary

4 segtrees

valves segtree, just normal purq segtree

length segtree, just normal purq segtree

(before update point X, call sumSegtree->query(X,X) to propo all)

86 of 110

Subtask 10: Summary

sumSegtree:

addLength: set flag to be X

addPoint: propo down to point, every node that it crosses add X

propo function: val+=length->query(s,e)

query normally

87 of 110

Subtask 10: Summary

rmqSegtree:

store: deficit as (valve-height)/length

addEntireRange: deficit+=X

addPoint: deficit-=X/length

changing the length: deficit*length/newlength

set to zero: length=0, deficit=-inf

(rmbr to check if length=0 to avoid div by 0 lmao)

88 of 110

Subtask 10: Summary

pour:

sumSegtree->upd(L,R,X)

rmqSegtree->upd(L,R,X)

open:

add to set, if it is new lowest,

sumSegtree->updPoint(i,old-new)

rmqSegtree->addPoint(i,old-new)

89 of 110

Subtask 10: Summary

close

remove from set, if it was the lowest, (we break up the segments)

sumSegtree->updPoint(i,old-new)

rmqSegtree->addPoint(i,old-new)

sumSegtree->querySum(i)

sumSegtree->querySum(tail) (tail refers to ending point of segment)

(this is to propagate the length stuff)

length->upd(i,new length)

length->upd(tail,new length)

change length on rmq segtree

90 of 110

Subtask 10: Summary

query

valves->query(L,R)+sumSegtree->query(L,R)

then after each operation:

while true, check if RMQ segtree return is >=0; if no, break, else it means overflowed.

calculate overflow by deficit*length.

find next non-overflow and throw it there by updating:

sumSegtree->addPoint(next,next,overflow)

rmqSegtree->addPoint(next,next,overflow)

rmqSegtree->changeLength(currLen+len of overflowed segment)

rmqSegtree->setZero(overflowed segment)

upd their respective lengths in the length segtree

91 of 110

Now go upsolve :)

92 of 110

Hungry Rabbits 3

The Rabbits are hungry again

93 of 110

Abridged Statement

There are n piles of carrots in a row. The i-th such pile has ci carrots.

Every minute, a rabbit can be placed at either pile 1 or pile n. Rabbits starting from pile 1 move right every minute, while those from pile n move left every minute.

Every rabbit eats a carrot from the current pile before moving.

Determine the maximum minimum amount of time that can pass before all carrots are eaten.

94 of 110

Subtask 2: n = 1

yea.

95 of 110

Observation 1

If we finish at time T, any rabbit that was released earlier than T - n will have gone over every single pile no matter where it started.

96 of 110

Subtask 3: n ≤ 15

We can brute force all 2n possible ways to place the last n rabbits.

Then, we compute find the largest pile left and that will be the number of additional rabbits needed.

Note that we need to handle the case of t ≤ n separately, but that doesn’t affect the final time complexity of O(n 2n).

97 of 110

Observation 2

If we can finish at time T, we can finish at a time T’ > T

Similarly, if we cannot finish at time T, we cannot finish at time T’ < T

Thus, we can binary search the answer.

98 of 110

Subtask 4: n ≤ 1000

We BSTA on the time we need T. We now want to clear all piles by time T.

We subtract max(0, T-n+1) carrots from each pile to account for the rabbits that will clear all piles. Our goal is to make every pile 0.

Let us look at the last n-1 rabbits. The i-th last rabbit to be placed will cover exactly i piles from the side they come from. From now on this is rabbit i.

99 of 110

Subtask 4: n ≤ 1000

For every rabbit i > n/2, they will always cover some middle section.

100 of 110

Subtask 4: n ≤ 1000

Thus, we can treat each such range as a range smaller than n/2 and subtracting one from the sections that will be covered regardless of which direction it comes from.

101 of 110

Subtask 4: n ≤ 1000

Now for each range we can either let it start from the left or right and it will subtract 1 from a prefix/suffix

102 of 110

Subtask 4: n ≤ 1000

We can “shorten” a range as needed.

1

0

1

103 of 110

Subtask 4: n ≤ 1000

We process from the middle outwards. In each step, we attempt to clear the piles on both sides that we are processing.

104 of 110

Subtask 4: n ≤ 1000

We also maintain a counter storing the number of ranges we can use to subtract 1.

105 of 110

Subtask 4: n ≤ 1000

If we have values > 0, we must use ranges to remove them until they are 0.

1

3

106 of 110

Subtask 4: n ≤ 1000

We have at most n ranges. Each range subtracts from at most n piles.

Time complexity is O(n2 log 109)

107 of 110

Subtask 5: n ≤ 100000

Use a fenwick. Time complexity O(n log n log 109)

108 of 110

Subtask 6: n ≤ 1000000

Don’t use a fenwick.

Use a cumulative sum of the number of ranges used on each side so far.

Time complexity O(n log 109)

109 of 110

Subtask 6: n ≤ 1000000 but fast

If the maximum size of a pile is K, we use at least K minutes and at most K - n + 1 minutes.

Time complexity O(n log n)

110 of 110

Subtask 6: n ≤ 1000000

The intended solution is an inequality bash. Won’t be presented because it would take too long to explain.