• No results found

Solution to Non-Starred Exercises

In document Competitive Programming 3 (Page 89-94)

2.5

Solution to Non-Starred Exercises

Exercise 2.2.1*: Sub-question 1: First, sort S in O(n log n) and then do an O(n) linear scan starting from the second element to check if an integer and the previous integer are the same (also read the solution for Exercise 1.2.10, task 4). Sub-question 6: Read the opening paragraph of Chapter 3 and the detailed discussion in Section 9.29. Solutions for the other sub-questions are not shown.

Exercise 2.2.2: The answers (except sub-question 7): 1. S & (N − 1) 2. (S & (S− 1)) == 0 3. S & (S− 1) 4. S  (S + 1) 5. S & (S + 1) 6. S  (S − 1)

Exercise 2.3.1: Since the collection is dynamic, we will encounter frequent insertion and deletion queries. An insertion can potentially change the sort order. If we store the informa- tion in a static array, we will have to use one O(n) iteration of an insertion sort after each insertion and deletion (to close the gap in the array). This is inefficient!

Exercise 2.3.2:

1. search(71): root (15)→ 23 → 71 (found) search(7): root (15) → 6 → 7 (found)

search(22): root (15)→ 23 → empty left subtree (not found). 2. We will eventually have the same BST as in Figure 2.2.

3. To find the min/max element, we can start from root and keep going left/right until we encounter a vertex with no left/right subtrees respectively. That vertex is the answer. 4. We will obtain the sorted output: 4, 5, 6, 7, 15, 23, 50, 71. See Section 4.7.2 if you are

not familiar with the inorder tree traversal algorithm.

5. successor(23): Find the minimum element of the subtree rooted at the right of 23, which is the subtree rooted at 71. The answer is 50.

successor(7): 7 has no right subtree, so 7 must be the maximum of a certain subtree. That subtree is the subtree rooted at 6. The parent of 6 is 15 and 6 is the left subtree of 15. By the BST property, 15 must be the successor of 7.

successor(71): 71 is the largest element and has no successor. Note: The algorithm to find the predecessor of a node is similar. 6. delete(5): We simply remove 5, which is a leaf, from the BST

delete(71): As 71 is an internal vertex with one child, we cannot simply delete 71 as doing so will disconnect the BST into two components. We can instead reshuffle the subtree rooted at the parent of 71 (which is 23), causing 23 to has 50 as its right child.

7. delete(15): As 15 is a vertex with two children, we cannot simply delete 15 as doing so will disconnect the BST into three components. To deal with this issue, we need to find the successor of 15 (which is 23) and use the successor to replace 15. We then delete the old 23 from the BST (not a problem now). As a note, we can also use predecessor(key) instead of successor(key) during delete(key) for the case when the key has two children.

Exercise 2.3.3*: For Sub-task 1, we run inorder traversal in O(n) and see if the values are sorted. Solutions to other sub-tasks are not shown.

Exercise 2.3.6: The answers:

1. Insert(26): Insert 26 as the left subtree of 3, swap 26 with 3, then swap 26 with 19 and stop. The Max Heap array A now contains {-, 90, 26, 36, 17, 19, 25, 1, 2, 7, 3}. 2. ExtractMax(): Swap 90 (maximum element which will be reported after we fix the

Max Heap property) with 3 (the current bottom-most right-most leaf/the last item in the Max Heap), swap 3 with 36, swap 3 with 25 and stop. The Max Heap array A now contains {-, 36, 26, 25, 17, 19, 3, 1, 2, 7}.

Exercise 2.3.7: Yes, check that all indices (vertices) satisfy the Max Heap property. Exercise 2.3.16: Use the C++ STL set (or Java TreeSet) as it is a balanced BST that supports O(log n) dynamic insertions and deletions. We can use the inorder traversal to print the data in the BST in sorted order (simply use C++ iterators or Java Iterators). Exercise 2.3.17: Use the C++ STL map (Java TreeMap) and a counter variable. A hash table is also a possible solution but not necessary for programming contests. This trick is quite frequently used in various (contest) problems. Example usage:

char str[1000];

map<string, int> mapper; int i, idx;

for (i = idx = 0; i < M; i++) { // idx starts from 0 scanf("%s", &str);

if (mapper.find(str) == mapper.end()) // if this is the first encounter // alternatively, we can also test if mapper.count(str) is greater than 0 mapper[str] = idx++; // give str the current idx and increase idx }

Exercise 2.4.1.3: The graph is undirected.

Exercise 2.4.1.4*: Subtask 1: To count the number of vertices of a graph: Adjacency Matrix/Adjacency List → report the number of rows; Edge List → count the number of distinct vertices in all edges. To count the number of edges of a graph: Adjacency Matrix → sum the number of non-zero entries in every row; Adjacency List → sum the length of all the lists; Edge List→ simply report the number of rows. Solutions to other sub-tasks are not shown.

Exercise 2.4.2.1: For int numDisjointSets(), use an additional integer counter numSets. Initially, during UnionFind(N), set numSets = N. Then, during unionSet(i, j), decrease numSets by one if isSameSet(i, j) returns false. Now, int numDisjointSets() can sim- ply return the value of numSets.

2.5. SOLUTION TO NON-STARRED EXERCISES  Steven & Felixc

For int sizeOfSet(int i), we use another vi setSize(N) initialized to all ones (each set has only one element). During unionSet(i, j), update the setSize array by performing setSize[find(j)] += setSize[find(i)] (or the other way around depending on rank) if isSameSet(i, j) returns false. Now int sizeOfSet(int i) can simply return the value of setSize[find(i)];

These two variants have been implemented in ch2 08 unionfind ds.cpp/java.

Exercise 2.4.3.3: RSQ(1, 7) = 167 and RSQ(3, 8) = 139; No, using a Segment Tree is overkill. There is a simple DP solution that uses an O(n) pre-processing step and takes O(1) time per RSQ (see Section 9.33).

Exercise 2.4.4.1: 90 - LSOne(90) = (1011010)2 - (10)2 = (1011000)2 = 88 and 90 + LSOne(90) = (1011010)2 + (10)2 = (1011100)2 = 92.

Exercise 2.4.4.2: Simple: shift all indices by one. Index iin the 1-based Fenwick Tree now refers to index i− 1 in the actual problem.

Exercise 2.4.4.3: Simple: convert the floating point numbers into integers. For the first task, we can multiply every number by two. For the second case, we can multiply all numbers by one hundred.

Exercise 2.4.4.4: The cumulative frequency is sorted, thus we can use a binary search. Study the ‘binary search for the answer’ technique discussed in Section 3.3. The resulting time complexity is O(log2n).

2.6

Chapter Notes

The basic data structures mentioned in Section 2.2-2.3 can be found in almost every data structure and algorithm textbook. References to the C++/Java built-in libraries are avail- able online at: www.cppreference.com and java.sun.com/javase/7/docs/api. Note that although access to these reference websites are usually provided in programming contests, we suggest that you try to master the syntax of the most common library operations to minimize coding time during actual contests!

One exception is perhaps the lightweight set of Boolean (a.k.a bitmask). This unusual technique is not commonly taught in data structure and algorithm classes, but it is quite important for competitive programmers as it allows for significant speedups if applied to certain problems. This data structure appears in various places throughout this book, e.g. in some iterative brute force and optimized backtracking routines (Section 3.2.2 and Section 8.2.1), DP TSP (Section 3.5.2), DP with bitmask (Section 8.3.1). All of them use bitmasks instead of vector<boolean> or bitset<size> due to its efficiency. Interested readers are encouraged to read the book “Hacker’s Delight” [69] that discusses bit manipulation in further detail.

Extra references for the data structures mentioned in Section 2.4 are as follows. For Graphs, see [58] and Chapters 22-26 of [7]. For Union-Find Disjoint Sets, see Chapter 21 of [7]. For Segment Trees and other geometric data structures, see [9]. For the Fenwick Tree, see [30]. We remark that all our implementation of data structures discussed in Section 2.4 avoid the usage of pointers. We use either arrays or vectors.

With more experience and by reading the source code we have provided, you can master more tricks in the application of these data structures. Please spend time exploring the source code provided with this book at sites.google.com/site/stevenhalim/home/material.

There are few more data structures discussed in this book—string-specific data structures (Suffix Trie/Tree/Array) are discussed in Section 6.6. Yet, there are still many other data structures that we cannot cover in this book. If you want to do better in programming contests, please research data structure techniques beyond what we have presented in this book. For example, AVL Trees, Red Black Trees, or even Splay Trees are useful for certain problems that require you to implement and augment (add more data to) balanced BSTs (see Section 9.29). Interval Trees (which are similar to Segment Trees) and Quad Trees (for partitioning 2D space) are useful to know as their underlying concepts may help you to solve certain contest problems.

Notice that many of the efficient data structures discussed in this book exhibit the ‘Divide and Conquer’ strategy (discussed in Section 3.3).

Statistics First Edition Second Edition Third Edition

Number of Pages 12 18 (+50%) 35 (+94%)

Written Exercises 5 12 (+140%) 14+27*=41 (+242%) Programming Exercises 43 124 (+188%) 132 (+6%)

The breakdown of the number of programming exercises from each section is shown below: Section Title Appearance % in Chapter % in Book

2.2 Linear DS 79 60% 5%

2.3 Non-Linear DS 30 23% 2%

Problem Solving Paradigms

If all you have is a hammer, everything looks like a nail — Abraham Maslow, 1962

In document Competitive Programming 3 (Page 89-94)