1 of 69

December Course 2024

Contest Analysis: DS III

2 of 69

Apology

Sorry

Other editor note: we are not sorry

3 of 69

Scoreboard

4 of 69

shamelessad4

mfw q1 isn’t trivial

5 of 69

Abridged Statement

Given an array (of positive integers), support the following operations:

  1. Multiply all values within range by k
  2. Output sum of range
  3. Output product of range

6 of 69

Subtask 1: Only output sum of range

This is quite literally just static range sum query.

If you can’t solve this, you shouldn’t be in advanced.

(Sorry for being harsh)

Use prefix sums to AC.

Remember to mod!

7 of 69

Subtask 2: Point multiply update, range sum query only

Use your preferred DS 2 technique to update a point to its new value, then take the range sum as per normal.

If you wish to use fenwick tree without modifying too much, you can use sum(L, L) to get the value at a point, then update by adding (K-1) * L.

Remember to mod!

8 of 69

Subtask 3: Range mult upd, range sum query only

We do lazy propagation.

We only need to maintain the sum of each node’s range.

When we apply a range multiply by k, the sum is also multiplied by k.

As for stacking, if we range multiply by X, then by Y, we get a range multiply of (X * Y).

9 of 69

Subtask 3: Range mult upd, range sum query only

if (s != e && lazy != 1){

l->sum *= lazy;

r->sum *= lazy;

l->lazy *= lazy;

r->lazy *= lazy;

lazy = 1;

} //anything missing?

10 of 69

Subtask 3: Range mult upd, range sum query only

if (s != e && lazy != 1){

l->sum *= lazy; l->sum %= MOD;

r->sum *= lazy; r->sum %= MOD;

l->lazy *= lazy; l->lazy %= MOD;

r->lazy *= lazy; r->lazy %= MOD;

lazy = 1;

} //lol don’t forget to modulo

11 of 69

Subtask 4: Point mult update, range sum query, range product query

Point update. We just maintain a DS II segment tree with two values instead, a sum and a product.

product = (l->product * r->product)%MOD;

sum = (l->sum + r->sum)%MOD;

12 of 69

Subtask 5: Everything all at once (sorry, tho it’s only 10 pts)

The main issue is this is your propagation function.

How do you predict how the product will change when you multiply all values by K?

13 of 69

Subtask 5: Everything all at once (sorry, tho it’s only 10 pts)

Consider a range with elements {1, 3, 7}.

Initially, product is 21.

What happens if we multiply throughout by 2?�{2, 6, 14}

Product is 168.

168 = 8 * 21 = (2^3) * 21

!!!

14 of 69

Subtask 5: Everything all at once (sorry, tho it’s only 10 pts)

General formula:�Multiplying all values by K on a range of length L results in this:�product *= (K ^ L);

We can’t naively calculate this, as it would be too slow!

15 of 69

Subtask 5: Everything all at once (sorry, tho it’s only 10 pts)

We apply binary/fast exponentiation to solve the problem.

Each propagate function call now takes O(logN) time, so the whole segment tree slows down to O(log^2 N) for each query/update.

16 of 69

cokemoresugar

mfw q2 isn’t trivial either

17 of 69

Abridged Statement:

You have two arrays F and S of length N.

Support the following operations:

  1. Add K to all F values within a given range
  2. Add K to all S values within a given range
  3. Find the sum of (Fi * Si) over all i within a given range

18 of 69

Subtask 1: No updates to F and S

This is another version of static range sum queries�:clown:

Just construct an array A where Ai = (Fi * Si).

Then all queries can be answered by prefix sum on A.

19 of 69

Subtask 2: All updates are point updates

Use a standard segment tree, with a value sumProduct where:

sumProduct = l->sumProduct + r->sumProduct

We also maintain global arrays F and S.

When we get to a leaf node, we can just calculate its value by multiplying F and S together at that leaf node’s position after updating the arrays.

20 of 69

Subtask 3: Only point queries

This subtask is a little troll.�We just need to maintain F and S, because we can answer all queries just using that array.

To maintain F and S, instead of using arrays, we can use a Range Update Point Query Fenwick/Segment tree to solve.

21 of 69

Subtask 4: Anything goes

The issue is once again how we store values and propagate.

If we want to maintain sumProduct, we need to know how it affects sumProduct when we do an update.

22 of 69

Subtask 4: Anything goes

Initially, sumProduct = F1 * S1 + F2 * S2 + F3 * S3

If we add 5 to all F, in the range [1, 3]

sumProduct = (F1+5)*S1 + (F2+5)*S2 + (F3+5)*S3

= F1*S1 + 5*S1 + F2*S2 + 5*S2 + F3*S3 + 5*S3

= F1*S1 + F2*S2 + F3*S3 + 5*(S1 + S2 + S3)

= initial sumProduct + 5 * sum of S

23 of 69

Subtask 4: Anything goes

In general, if we update a node by adding K to F,

its sumProduct increases by K * (sum of S).

Using the same idea, we can get that

if we update a node by adding K to S,

its sumProduct increases by K * (sum of F).

24 of 69

Subtask 4: Anything goes

How to get sum of S and sum of F?�We maintain it as other values in the segment tree node!

Sum of S and sum of F are not difficult to maintain when updates occur.

Now we know how to maintain sumProduct, sumS,

and sumF!

25 of 69

Subtask 4: Anything goes

So how does stacking and lazy propagation work?

We store lazy as a pair of {changeF, changeS}.

Let’s consider how this stacks.

Compare the following:�Change F, then change S

Change S, then change F

There’s no difference in the final result!

26 of 69

Subtask 4: Anything goes

Stacking can then just be done independently for each

F and S. When propagating, we just do lazy.first and

lazy.second separately, and the answer will work out.

27 of 69

gohcart2

progression_easy

28 of 69

Abridged Statement

You have an array of booleans.

Support the following queries:

  1. Flip all booleans within a range. (true -> false, false -> true) (logical not)
  2. Query for the longest consecutive segment of true within a range.

29 of 69

Subtask 1: Flip whole array, query whole array

Truly one of the problems of all time

Note that there are only two possible states of the array, one before flipping and one after flipping.

Hence, we can just compute the longest subarray of 1s of both possible arrays in O(n) time to solve the problem.

(Just iterate through lol)

30 of 69

Side Note: Contest Strategy in NOI

It’s 3 points, do you still grab it?

ABSOLUTELY YES!! You will not know medal cutoff.

31 of 69

Subtask 2: Point query

Truly one of the problems of all time

Query boils down to checking if that point is 0 or 1

�We can just maintain a Range Update Point Query Fenwick tree. A range update is add 1 to range. Then the value is 1 at a point if the value at that point is odd, 0 otherwise.

32 of 69

Subtask 2: Point query

Truly one of the problems of all time

Query boils down to checking if that point is 0 or 1

�We can just maintain a Range Update Point Query Fenwick tree. A range update is add 1 to range. Then the value is 1 at a point if the value at that point is odd, 0 otherwise.

33 of 69

Subtask 3: No updates

We consider a segment tree’s node values.

In order to maintain max num of 1s (mx1), we need the following:

mx1 = max( { l->mx1, r->mx1, l->suffix1 + r->prefix1 } );

prefix1 is the longest prefix of 1s

suffix1 is the longest suffix of 1s

34 of 69

Subtask 3: No updates

mx1 = max( { l->mx1, r->mx1, l->suffix1 + r->prefix1 } );

The l->mx1 and r->mx1 is obvious

However, a new better subarray may be created by crossing between l and r

L R

[0, 1, 1, 1, 0, 1, 1] [1, 1, 0, 0, 1, 1, 1]

l->mx1 = 3 r->mx1 = 3

35 of 69

Subtask 3: No updates

Now that means we need to maintain longest prefix and suffix of 1s!

Notice that the longest prefix is almost always equal to the left segment’s longest prefix.

[1, 1, 0, 0, 1, 0] [1, 1, 0, 1, 0, 0]

However, there is one case where this is untrue.

When the entire left segment is 1!

36 of 69

Subtask 3: No updates

[1, 1, 1, 1, 1, 1] [1, 1, 0, 1, 0, 0]

In this case, the longest prefix of 1s is

l->prefix + r->prefix

(equivalent to l->size + r->prefix)

37 of 69

Subtask 3: No updates

Do the same for the suffix case.

Now, we can maintain the solution yay

38 of 69

Subtask 4: Full solution

nearly done

Now, to maintain the number of consecutive 1s after a flip, notice that this number is equivalent to the number of consecutive 0s before the flip.

In other words,

swap(pref1, pref0)

swap(suff1, suff0)

swap(mx1, mx0)

39 of 69

Subtask 4: Full solution

Ok now we just maintain everything and we win yay

40 of 69

morexiaoyang

btw, xiaoyang did approve to use his name

41 of 69

Abridged Statement

There are N points on a huge grid.

Pick a point, such that when a vertical line and horizontal line passes through it, it splits the points into 4 quadrants.

Your score is the minimum of num of points in each quadrant.

Maximise score. (and find the point that does so)

42 of 69

Subtask 1: N <= 500

We only care about x-values and y-values that exist at some point within the N points. (Line sweep idea)

This is because as we change X for example, if we do not pass over a point’s x-coordinate, our answer remains the same.

43 of 69

Subtask 1: N <= 500

Hence, we generate all possible X and Y coordinates, bruteforce all pairs of them, and check in O(N).

44 of 69

Subtask 2: N <= 2000

Very cringe

Discretise, then do 2D prefix sum or fenwick tree or something lah

Just optimise your checking to O(1).

45 of 69

Subtask 3: Xi is 0 or 1

Notice that if we choose X == 0, the left two quadrants always get 0.�Hence, our X must be 1.

We try every Y, then check minimum quadrant sum using prefix sums.

46 of 69

Subtask 4: n <= 4*10^4

Subtask 5: 0 <= Xi, Yi <= 10^6

Subtask 6: t = 1

Subtask 7: No additional constraints

Waw

47 of 69

Everything

Firstly, we discretise to make our constant time much better.

Then, we binary search the answer.

Then, we line sweep.

48 of 69

Everything

Given a guess G, we first iterate through all possible X values.

Then, we determine if it is possible to find a Y-value such that we get at least G in each quadrant.

The bottom-left and bottom-right both have a minimum Y-value such that they are fulfilled.

The top-left and top-right both have a maximum Y-value such that they are fulfilled.

49 of 69

Everything

The bottom-left and bottom-right both have a minimum Y-value such that they are fulfilled.

The top-left and top-right both have a maximum Y-value such that they are fulfilled.

Anything higher than the minimum and lower than the maximum will pass.

Hence, we just need to find the minimum and maximum to determine if a valid Y exists for this X.

50 of 69

Everything

BSTA on segment tree/fenwick tree!�Determine the minimum index Y1 such that pref[Y] >= G for both bottom-left and bottom-right.

Then, determine the maximum index Y2 such that suff[Y] >= G for both top-left and top-right.

It is only possible if Y1 < Y2.

51 of 69

Everything

Subtask 4 is for O(N log^3 N) solutions

Subtask 5 is for solutions that fail to discretize.

Subtask 6 is for solutions that fail to handle multitest.

52 of 69

fakeidol

google it

53 of 69

Abridged Statement

You have a permutation A. Support swap updates.

After each swap, construct an array B such that:

  • if A[i] > A[j], B[i] >= B[j]
  • if i > j, B[i] >= B[j]

Output the maximum number of distinct elements in an optimal construction. (you don’t have to output the construction.)

54 of 69

I have no idea how to do any of the subtasks.

Observation (ie magic): The answer is the number of prefixes that contain all numbers from 1 to the size of the prefix

Proof: the only possible time you can increase a score is when everyone before you has a smaller value than everyone after this point.

55 of 69

I have no idea how to do any of the subtasks.

Observation 2: A sufficient condition to check whether a prefix is a valid permutation of consecutive numbers is whether sum(prefix) = i*(i+1)/2, where i is the length of the prefix.

Observation 3: All numbers are positive and distinct. Hence sum(prefix) >= i*(i+1)/2 ie sum(prefix) - i*(i+1)/2 >= 0

56 of 69

I have no idea how to do any of the subtasks.

Hence we store a segment tree where tree[i] = -i*(i+1)/2 + prefix sum of A up to i

This can be done using range updates in O(log N)

Query is the number of 0s in the leaves of the segment tree, which is the number of minimums in the segtree.

57 of 69

I have no idea how to do any of the subtasks.

Swap updates is just updating the prefix sum array, can be done with range updates.

58 of 69

Alternate Solution (With subtask sols)

You still need the first observation: That the answer is the number of prefixes i where the set

{A[1], A[2], …, A[i]} = {1, 2, …, i}

This observation can be motivated by seeing that if u occurs before v, and u>v then u and v have to be the same value. This is just a natural result of that.

59 of 69

Alternate Solution (ST 1 & 2)

Brute force O(NQ) will pass the first 2 subtasks.

60 of 69

Alternate Solution

We need to find a fast way to check our condition:

{A[1], A[2], …, A[i]} = {1, 2, …, i}

One way to approach this would be to count the number of values in A[1] to A[i] that are not in the range [1, i].

61 of 69

Alternate Solution

One way to approach this would be to count the number of values in A[1] to A[i] that are not in the range [1, i] in segtree[i].

It turns out that this is relatively easy to maintain, you only really need to support range adds (left as exercise to reader). The problem becomes counting the number of 0s in the segment tree.

62 of 69

Alternate Solution

Counting the number of occurrences of a certain element in a segment tree is not a easy problem.

It turns out that you can calculate this quickly because 0 is the smallest number in the segment tree. But what if you didn’t notice this?

63 of 69

Alternate Solution (ST 5, adj swaps)

Swaps are all adjacent

If you think about what is stored in your segment tree, you can see that this means at most 2 values in your segment tree change after every update.

So the number of 0s can be maintained quite easily here.

64 of 69

Alternate Solution (ST3, N, Q 105)

We want to count the number of occurrences of a number in a segment tree, and we need to support range add updates.

This is solvable with Square Root Decomposition, namely buckets. (You should probably read the slides in extra lecture first)

65 of 69

Alternate Solution (ST3, N, Q 105)

Build sqrt(N) buckets, each of size sqrt(N), call this B.

The ith bucket corresponds to the range [(i-1)*B, i*B], and will store the number of occurrences of each value from 0 to N in the elements of the list with those indices.

66 of 69

Alternate Solution (ST3, N, Q 105)

When an update covers an entire bucket, you can just store the “offset” in O(1), so you don’t need to manually update the occurrence array(which would take O(sqrtN) time per bucket).

This is similar to what you do for https://codebreaker.xyz/problem/cups

When you query that bucket just remember about the offset.

67 of 69

Alternate Solution (ST3, N, Q 105)

The number of fully covered buckets is at most sqrtN, so we did updates on fully covered buckets in O(sqrtN)

If an update only covers part of a bucket, just rebuild the bucket. This takes O(sqrtN) time and occurs at most 2 times per update.

68 of 69

Alternate Solution (ST3, N, Q 105)

Time complexity O((N+Q)sqrtN)

69 of 69

Full solution

Same as in the other solution.

Store the min, and the number of occurrences of the min.