P8187 [USACO22FEB] Robot Instructions S Solution
The content below was generated entirely by machine translation. Please verify its accuracy. If anything is unclear, consult the Chinese source version.
Contents:
- Brute force
- Meet-in-the-middle search + map or hash table
- Meet-in-the-middle search + two pointers + a
different from DFSstrange state-enumeration method- First two-pointer method
- Second two-pointer method
- Complete code
The reading experience is better on the blog.
1. Problem Statement
You are given two-dimensional vectors. For every with , determine how many selection schemes choose exactly of the vectors such that their sum is .
2. Analysis
2.1 Brute-Force Algorithm
On seeing this problem, we can quickly think of a partial-score solution: brute-force every possible selection scheme, test whether the selected vectors sum to the target vector, and add every valid scheme to the answer. However, , and this algorithm has complexity , so it will certainly time out.
2.2 Meet-in-the-Middle Search + Map or Hash Table
2.2.1 Brief Idea
Meet-in-the-middle search in this problem means dividing the vectors into two parts. For each part, brute-force all possible selection schemes, then store those schemes and the result of each scheme—their vector sum—in some form. Finally, match schemes from the two parts and add those whose combined sum equals to the answer.
2.2.2 Details
To achieve the effect described above, we can use an STL map or unordered_map to store every selection scheme. A hand-written hash table also works, although it may take more time to implement. I recommend unordered_map, because map has complexity and will be too slow for this problem, while the ideal complexity of unordered_map and a hand-written hash table is . Of course, when using unordered_map, the hash function must be good enough to avoid being defeated by the tests—for example, my current version cannot pass.
The usual way to brute-force the states is DFS, which is also relatively easy to write. The two-pointer section of this solution introduces a rather strange method; readers interested in it can skip ahead.
Note: below, “map” refers collectively to unordered_map, map, or a hand-written hash table.
First create two maps, fir and sec, which store selection schemes for the first and second halves of the vectors, respectively. The key for each map can be a structure containing three integers, . Here, is the sum of all vectors selected by the current scheme, and is the number of vectors selected by that scheme. Many schemes may have exactly the same , , and , so the value in the map is the number of schemes sharing those three values.
To store the answer, create an array ans[n], where ans[i] denotes the number of selection schemes when .
After finding the schemes for both parts, we combine valid pairs and add them to the answer. Every scheme stored in a map contains its vector sum . Let the vector sum of a scheme from the first half be and that of a scheme from the second half be . If , record this combination in the answer.
Because we use maps, we do not need a genuine nested loop that traverses every pair of schemes. For a possible match, . We can use a double loop whose first level enumerates the fir map and whose second level enumerates the current . Inside the loop, write conceptually:
ans[current k] += it_fir.value * sec[{x_g - it_fir.key.x, y_g - it_fir.key.y, current k - it_fir.key.k}]
Here, it_fir is an iterator over fir. The scheme represented by sec[{x_g - it_fir.key.x, y_g - it_fir.key.y, current k - it_fir.key.k}] and the scheme currently visited by it_fir satisfy , while the total number of selected vectors in the two schemes equals the current . Since the values of the two maps are the numbers of schemes satisfying those conditions, multiply the two values to obtain the number of all valid pairings.
2.3 Meet-in-the-Middle Search + Two Pointers
The theoretical complexity of two pointers appears to be the same as that of a hash table, but a poor hash function can make a hash table much slower. Two pointers do not have this problem.
2.3.1 Two Pointers A
First create two vectors, fir and sec. Their element type is the same as the map key above: . fir stores schemes produced from the first half of the vectors, while sec stores those from the second half.
After enumerating every scheme, sort both vectors according to the following rule:
1 | if(a.x != b.x) return x < b.x; |
Then create two pointers. Initialize to 1 and to sec.size() - 1, the last element of sec. At this point, points to the smallest element in fir, and points to the largest element in sec.
Consider how to make the sum of the vectors and at the current pointers equal . If we need to increase , we can only increase , because already points to the largest element of its array. Conversely, if we need to decrease , we can only decrease . In code, this is:
1 | int p1 = 0, p2 = sec_half.size() - 1; |
Note: Instruct is a structure containing the three integers {x, y, k}.
This method guarantees that we eventually find cases where . However, both arrays may contain a consecutive run of completely identical values, meaning that multiple pairs satisfy the equation. We therefore need to find the exact boundaries of this valid consecutive run.
The preceding code has already found the smallest valid and the largest valid . To determine the full range, we also need the largest valid and the smallest valid . This is simple: every value in the consecutive run is identical, so we only need to compare each current element with the first element of the run.
We must also add valid pairings to answers grouped by . Create two arrays, fir_same_k and sec_same_k. fir_same_k[i] is the number of entries with in the valid run of the first array, and sec_same_k has the corresponding meaning for the second array.
This yields the following code. Insert it after the else if in the preceding snippet:
1 | else{ |
This method runs relatively quickly; see the submission record.
2.3.2 Two Pointers B
In method A, counting the answers requires the arrays fir_same_k and sec_same_k to group cases with the same . We can improve the method by grouping schemes with the same at the time states are enumerated.
More specifically, change the vectors fir and sec into vector<Instruct> fir[20], sec[20]. fir[i] stores all first-half schemes with , and sec[i] stores the second-half schemes with . Since the storage method changes, the later two-pointer stage must change as well.
This time, use a double loop to enumerate different fir[i] and sec[j] arrays. Inside the loop, perform work similar to method A. In other words, because the values of the current first-half and second-half schemes are already known, two pointers need only find the ranges of and satisfying .
1 | for(int fir_k = 0; fir_k <= n / 2 + 1; fir_k++){ // Enumerate k for the first half. |
What advantage does method B have over method A? It saves space. With method A, the structure storing a scheme must contain the three integers . Note that is at most 20, yet an int or short must still be used to store it. Because 20 is so small, either data type wastes a large amount of space. With method B, the structure contains only the two integers ; is stored in the array index. As long as the array size equals the maximum , no space is wasted.
For a concrete comparison, see this submission record. Compared with method A, method B uses approximately 17 MB less memory.
There is a cost: method B is slightly slower. I estimate that this mainly comes from the two-pointer stage; the sorting stage is actually somewhat faster. In any case, both methods have the same theoretical complexity, because each selection scheme is visited at most once.
2.4 A Strange State-Enumeration Method
The common state-enumeration method for this problem is DFS. Here is a rather strange alternative. In a selection scheme, every vector has two states: selected or not selected. Because there are only these two states, a binary number can represent the complete state. Bit indicates whether vector is selected. For example, selects vectors 1 and 3 but not vector 2.
To enumerate every state, increment a number from 0 through and inspect whether each bit is 0 or 1 at every increment. Because we use meet-in-the-middle search here, the number only needs to reach .
In terms of complexity, this may even be slower than DFS, and it uses more code. Every increment still requires a loop that checks the number from its first bit through its twentieth bit. On the other hand, because it avoids recursion, it does not repeatedly allocate stack frames for recursive functions, so memory usage may be slightly lower.
I do not recommend writing this during a real contest, since DFS is genuinely convenient. This is included only as an entertaining alternative.
3. Complete Code
1 |
|
1 |
|
I kept debugging this method, but even now it fails the time or memory limit. My hash function is probably broken, but I do not know the correct way to write it. If you want to use this approach, you can read solutions by other experts. If you know the method and can help me debug it, I would be very grateful. Here is the submission record, and I put the code here.
3.1 Meet-in-the-Middle Search + Hash Table
Finally, I hope this solution is helpful. You can raise any questions in a private message or in the comments, and I will try my best to solve them.







![[Stanford CS144] Lab 4 Record](/img/CS144/tcp%E7%8A%B6%E6%80%81%E6%B5%81%E8%BD%AC%E5%9B%BE.jpg)