Editorial for COI '15 #1 Dijamant


Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.
Submitting an official solution before solving the problem yourself is a bannable offence.

The first step in solving this task is choosing which structure to use to store class names, in other words, to use to assign class names to ordinal numbers. One option is using map<string, int> in C++. When we recognize a new valid declaration, we assign the next ordinal number to the class name. The first two subtasks come down to checking whether the classes are declared, so this is easily solved now. As an alternative, for example when using the C programming language which doesn't have this data structure, we can first read, save and sort the names of all declared classes. The index of individual classes can then be efficiently found using binary search. In any case, the complexity of one query to the structure is \mathcal O(\log n).

For the other two subtasks, we need to consider the paths in the directed graph that models the class hierarchy. Let's assume that we are considering a new declaration K : P1 P2 ... Pk ; and first try to determine when a diamond A, B, X, Y is formed when adding it. Firstly, evidently it must hold K = B. Furthermore, there must exist a path from X to K and from Y to K so let X' and Y' be classes on these paths immediately before K. Since they are the last ones on the paths, classes X' and Y' need to be one of the classes P_1, P_2, \dots, P_k which K inherits. It is crucial to notice that classes X' and Y' cannot be derived one from another – in fact, when X' would, for example, be derived from Y' then A, X, Y, X' would be a diamond that already exists before adding the new declaration.

Therefore, when adding a new declaration, it is sufficient to check the following: Do two classes P_i and P_j exist among classes that K inherits and which are not derived from each other, and that are both derived from some class A? Since we are processing declarations, for each valid declaration of class K, we will calculate R_K – the set of all ordinal numbers of classes which K is derived from. A new declaration is not valid if there exist two classes P_i and P_j among classes that K inherits so that it holds P_i \notin R_{P_j} (P_j is not derived from P_i), P_j \notin R_{P_i} (P_i is not derived from P_j) and R_{P_i} \cap R_{P_j} \ne \emptyset (P_i and P_j are both derived from some class A). This logic can be directly implemented by considering a pair of classes P_i, P_j, which leads to an algorithm of the complexity \mathcal O(n^3), but can be optimized enough to score 100 points (for example, by using bitmasks).

The targeted solution efficiently checks whether classes P_i and P_j exist that satisfy the aforementioned conditions. Among other things, it is necessary to notice that if P_a is derived from P_b, then P_b doesn't even need to be considered. For each declaration, we do the following:

  1. Sort the classes P_1, P_2, \dots, P_k from the later declarations to the earlier ones.
  2. We maintain the set R that corresponds to the union of all R_{P_i} for classes P_i that we have considered thus far. The set R can be stored as an array of n booleans.
  3. For each class P_i,
    • If P_i is already in R, ignore it.
    • Otherwise, add all elements from R_{P_i} to R. If we encounter an element already in R while adding, then we found a diamond and the declaration is dismissed.

For each declaration, the number of processing steps is proportional to the total number of elements that are added to R, so it is bound by n. The total complexity is \mathcal O(n^2 \log n).


Comments

There are no comments at the moment.