A – Repeat ACL
Easy programming-language-knowledge check typical of the first task of AtCoder beginner contests. The following is a passing Kotlin submission:
fun main() {
val k = readLine()!!.toInt()
val ans = "ACL".repeat(k)
println(ans)
}
B – Integer Preference
Assume there is an integer in both ranges and call it $$$x$$$. Note that both $$$x \ge \max(A, C)$$$ and $$$x \le \min(B, D)$$$ must therefore be true.
Thus $$$x$$$ exists if and only if $$$\max(A, C) \le \min(B, D)$$$ is true. This is the standard formula for finding the overlap of two ranges.
C – Connect Cities
Note that the initial network of cities can be divided into one or more components, where any city of a component can reach any other city in that component following roads, but there are no roads connecting components together.
If there are $$$c$$$ components, Snuke can connect two of them by choosing one city from each component and building a road between them, which combines them to a single component; so the answer would be $$$c - 1$$$.
Components can be found using breadth-first/depth-first searches, however there's an easier way if you already have a special data structure known as DSU (disjoint-set union). And there is an implementation available right in the dsu
module of the AtCoder library. So start with $$$c := N$$$, for each road given in the input, you can use same
to check if the vertices are already connected; if they aren't, merge
them and decrement $$$c$$$.
Note that though the input is $$$1$$$-indexed, the AtCoder DSU implementation is $$$0$$$-indexed.
D – Flat Subsequence
Imagine an array $$$B$$$, initially filled with zeros. The answer then can be theoretically obtained with the following algorithm:
For each $$$a$$$ in $$$A$$$:
- Find $$$\displaystyle\max _{j = a-k} ^{a+k} B_j$$$. Let it be $$$r$$$
- Set $$$B_a := r+1$$$. This works because you are finding the longest subsequence that can include $$$a$$$, and then extending it.
Then the final answer is $$$\max B_i$$$.
Unfortunately this is too slow as searching for the maximum in a range naively is $$$O(K)$$$, thus making the final complexity $$$O(NK)$$$. However, there is a special data structure that solves this problem, called a segment tree.
Segment trees work by storing the array data in the leaves of a fixed binary tree, then storing a "sum" in the ancestor nodes of those leaves. Updating a single entry is slower than a normal array as $$$O(\log n)$$$ ancestors need to be updated, but querying the "sum" of a range becomes much faster, as only $$$O(\log n)$$$ nodes need to be accessed, and the data "summed".
The scare quotes around "sum" imply that it's a more general term — segment trees can indeed implement sums, but more generally, it can implement any monoid. Basically, it's a closed binary operation $$$(S \oplus S) \rightarrow S$$$ that is both associative and has an identity element.
As you are only storing non-negative integers, the $$$\max$$$ operator indeed has an identity element, $$$0$$$. Even if you needed to store negative integers, you can typically set the identity to any number lower than anything else you'd use (just like how you might use a large number as "infinity" in DP/greedy problems)
The segtree
module in the AtCoder library is an implementation of a segment tree. Be sure to read the documentation carefully; you need to set the operator and identity element. Ranges should be entered in half-open form, and be careful of accidentally exceeding the index limits in queries.
There is also a way to solve this using only an ordered map.
E – Replace Digits
Precalculate $$$10^x$$$ (under modulo, of course) for $$$0 \le x \le N$$$. Also precalculate $$$\displaystyle ones(x) = \sum _{j=0} ^{x-1} 10^j$$$ for $$$0 \le x \le N$$$, representing the value of a string of all ones of length $$$x$$$.
This problem should remind you a lot of the previous one, however you now have to also update on a range. This can be done using a lazy-propagating segment tree. It's "lazy" because range updates are not all done straight away, but could be "queued" up in an ancestor node, only being "pushed" toward the leaves when needed for queries.
There is a lazysegtree
in the AtCoder library as well. Configuring it for this problem is a little more involved than the previous one:
The monoid operator isn't a simple sum, but requires that the size of the segment to be known as well. Thus $$$S$$$ should be a tuple $$$(sum, size)$$$, with the operator $$$(sum_a, size_a) \oplus (sum_b, size_b) = (sum_a \cdot 10^{size_b} + sum_b, size_a + size_b)$$$. Its identity, $$$e = (0, 0)$$$. The $$$sum$$$ terms should be under modulo.
$$$F$$$, the mapping, can be a simple integer indicating the digit to update with, however a "null / identity map" needs to be assigned as well, this could be $$$id = -1$$$. $$$composition(g, f)$$$ should be $$$g$$$ (the newer update is on the left as in the traditional notation $$$(g \circ f)(x) = g(f(x))$$$). $$$mapping(f, s)$$$ should be $$$s$$$ if $$$f = id$$$, and otherwise $$$(f \cdot ones(size_s), size_s)$$$
The rest is then just implementing the task. Note that when initializing the segment tree, the value $$$(1, 1)$$$ needs to be set individually to all indices; you can't just queue a $$$1$$$ range-update as they start out as the empty segment $$$(0, 0)$$$.
You can also still solve this with an ordered map.
F – Heights and Pairs
Define a bad pair as a pair where the two persons' heights are equal.
Let's define a useful function $$$\displaystyle pairs(n) := \frac{(2n)!}{n!\cdot 2^n}$$$, the number of ways to make $$$n$$$ pairs (good or bad) out of $$$2n$$$ people. Explanation: Start with any of $$$(2n)!$$$ permutations. When you pair them up, you don't care about the order within the pair, so you divide by $$$2$$$ a total of $$$n$$$ times. Then you also don't care about the order between the pairs, so you divide by $$$n!$$$.
The main idea involves the Inclusion–exclusion principle.
In general, principle of inclusion-exclusion (PIE) can be summarized with the following steps:
- Identify a set of "violations", i.e. a set of rules you want to obey and specific requirements for each rule to be broken.
- Identify a way to calculate $$$F(S)$$$: for every subset $$$S$$$ of possible violations, the number of ways to violate at least that subset (note that we don't need to know how to calculate the number of ways to violate exactly that subset).
- The number of ways to obey all rules (i.e. not commit any violations) will then equal $$$(\sum _ {|S| \text{ is even}}F(S)) - (\sum _ {|S| \text{ is odd}}F(S)) $$$ (note that zero is also even)
In this problem there are up to $$$N$$$ possible violations, i.e. the $$$i$$$-th pair of people being of equal height, i.e. a bad pair. Note that we don't actually care about the order of the pairs, so we can just push all the "bad pairs" to the beginning, so we actually only have to worry about $$$O(N)$$$ possible subsets of violations. Let's define $$$G(k) = \sum _ {|S|=k}F(S)$$$, and calculate the final answer as $$$(\sum _ {k \text{ is even}}G(k)) - (\sum _ {k \text{ is odd}}G(k)) $$$
Let $$$f(k)$$$ be the number of ways to match up $$$k$$$ bad pairs from the $$$2N$$$ given people. We can thus see that $$$G(k) = f(k) \cdot pairs(N-k)$$$, note we don't care about accidentally making more bad pairs, as it's accounted for in the definitions of $$$F(S)$$$ and $$$G(k)$$$.
Now let's figure out how to find $$$f(k)$$$. First note that the exact heights don't matter, only their equality. We can divide the people into components with the same height as each other. Collect all component sizes into array $$$a$$$. For example, if $$$h = [1, 1, 1, 2, 2, 4]$$$, then $$$a = [3, 2, 1]$$$. This can be easily done using sorting and run-length compression.
Then from each component $$$a_i$$$, construct array $$$b_i$$$, indexed over $$$[0, \lfloor a_i/2 \rfloor]$$$, where $$$b_i[j]$$$ denotes how many ways are there to fix $$$j$$$ bad pairs from this component. $$$\displaystyle b_i[j] = \binom {a_i}{2j} \cdot pairs(j)$$$, since we take $$$2j$$$ people from the component and make $$$j$$$ pairs out of them.
In the case of a single component, $$$f(j) = b_1[j]$$$, but how do we combine the results of several components? Let's assume we have only two components. Then we may take $$$0$$$ bad pairs from one component and $$$j$$$ from the other, or $$$1$$$ from one and $$$j-1$$$ from the other, etc., therefore:
$$$\displaystyle f(j) = b_1[0] \cdot b_2[j] + b_1[1]\cdot b_2[j-1] + b_1[2]\cdot b_2[j-2] + ... + b_1[j]\cdot b_2[0] = \sum_{i=0}^j b_1[i] \cdot b_2[j-i] $$$.
This is called a convolution, and is the same formula for when we multiply two polynomials of the form $$$p(x) = B_0x^0 + B_1x^1 + B_2x^2 + ... + B_nx^n$$$ and wishing to find the $$$j$$$-th coefficient. The case with more components is similar, except we must convolve all the $$$b_i$$$ arrays, i.e. find the product of several polynomials of different lengths.
[One can visualize this by treating $$$x^k$$$ as a formal symbol meaning "ways to make $$$k$$$ bad pairs", which naturally add together when their coefficients are multiplied]
However, computing convolutions for two polynomials of size $$$n$$$ and $$$m$$$ naively is $$$O(nm)$$$, which is too slow. There is just the right tool in the AtCoder library for that; the function convolution
. This function uses the Fast Fourier Transform method to convolve two polynomials in $$$O((n+m) \log (n+m))$$$. However note that it only accepts two arguments, and convolving multiple polynomials could be inefficient if called in the wrong order; imagine if you had about $$$N/2$$$ polynomials, and all of them are short except for one. Then imagine if you convolved a short one to the longest one over and over; suddenly your submission takes $$$O(N^2 \log N)$$$ and times out.
Fortunately there is a simple fix: just put all the polynomials into a heap / priority queue that sorts them from shortest to longest. While there is more than one polynomial in that heap, withdraw two, convolve them, then put the result back in the heap.
This works in $$$O(N \log ^2 N)$$$, the worst case being $$$N$$$ polynomials of degree $$$1$$$ (length $$$2$$$), which would take $$$O(\log N)$$$ "cycles" to fully combine, with the convolutions taking a time of $$$O(N \log N)$$$ each cycle.
And thus finally, you are able to obtain $$$f(k)$$$ by reading the coefficients of the final polynomial, and can apply the inclusion-exclusion formula above to obtain the answer.