SRM 685, D1, 250-Pointer (MultiplicationTable2)

James S. Plank

Sat Oct 19 01:25:57 EDT 2019

In case Topcoder's servers are down

Here is a summary of the problem:

The examples

Example table Answer
0
{1,1,2,3,
 1,0,3,2,
 2,0,1,3,
 1,2,3,0}
2 (the set is {0,1})
1
{0,1,2,3,
 1,2,3,0,
 2,3,0,1,
 3,0,1,2}
1 (the set is {0})
2
{1,1,1,1,
 2,2,2,2,
 3,3,3,3,
 0,0,0,0}
4 (the set is {0,1,2,3})
3
{0}
1 (the set is {0})


Testing yourself

Like the Cryptography Problem, I have a shell script tests.sh, that you can use to test your program. When you run tests.sh, your answer should be identical to answers.txt. Mine runs in roughly three seconds.

Hints

A first approach based on the minimum closed set containing x:

Let's first wrap our heads around the problem. Let's define CS(x) to be the smallest closed subset that contains x. We can build CS(x) incrementally, in the following way:

  1. Start by setting CS(x) to { x } .
  2. Enumerate all pairs of elements (i,j) in CS(x). It is possible for i to equal j.
  3. Generate the set P where k ∈ P if and only if there is a pair of elements (i,j) such that i and j are both elements of CS(x), and i*j = k.
  4. Set CS(x) equal to ( CS(x) ∪ P ).
  5. If CS(x) got bigger, start again at step 2.
Let's generate CS(0) in example 0: You can program this approach directly, and it may or may not pass topcoder's tests, because it is really slow. Think about it -- in each iteration of the steps above, you are generating |CS(x)|2 pairs of values to test. Let's assume a pathelogical case, that every time, you add one element to CS(x), and that |CS(x)| will eventually equal n. Then you have:
- for every value of x                           # There are n of these
  - Calculate CS(x)                              # This will take n passes, because we add one element each time.
    - By enumerating all pairs in CS(x)          # There are n^2 pairs
      - For each pair, you insert into a set.    # This is log(n)
    -
  -
-
That's O(n4log(n)) which is pretty slow. I didn't test it against Topcoder, because I couldn't bring myself to write such a slow program. However, it is a sound approach -- we just need to make it faster.
A second approach: calculate only the potential new elements at each pass.

A big problem with the approach above is that it doesn't remember which pairs that it has already processed. For example, if you've already calculated 0*0 once, you don't need to calculate it again.

Here's an approach to reduce the running time. Once again, we are going to calculate CS(x) incrementally. We are going to maintain three data structures:

  1. CS(x) -- We'll use a set.
  2. To_Add -- This is a vector of new elements to add to CS(x).
  3. Next_To_Add -- This is a set which contains the elements that will go into To_Add on the next pass.
We start with CS and Next_To_Add being empty, and To_Add containing x. Then we repeat the following: Determining the running time here is a little more subtle -- instead of counting loops, you can count the total number of times that certain operations are done:
- for every value of x
  - Each potential element of CS(x) is only ever inserted into CS(x) once.
  - Each product i*j is only calculated once.
  - Each product is only ever inserted into Next_To_Add once.
-
This yields a running time of O(n3log(n)). Think of it as: