Fracturing Search
Author: Benjamin Qi
A simple solution to "Robotic Cow Herd" that generalizes.
Prerequisites
- Gold - Minimum Spanning Trees
- some familiarity with "Robotic Cow Herd" analysis
General Outline
Problem
Suppose that you have a rooted tree where each vertex has a value . Also, if is not the root then has a parent satisfying . Given that each vertex has at most children, find the smallest values in the tree.
Approaches
Approach 1: Use a priority queue initially containing only the root. At each step, extract the vertex with smallest value from the priority queue and insert all of its children into the queue. Since we insert vertices in the priority queue, this runs in time. You can think of this as Dijkstra on a tree.
Approach 2: Suppose that we know that the -th smallest value is an integer in the range . Then for any we can check whether there are less than values in the tree less than or equal to in time with a simple DFS that breaks once you find values. This approach runs in time.
We'll focus on the first approach.
Optional: A Faster Solution
There are ways to do this in time for a binary tree if you don't need to return the values in sorted order (see here).
Generalizing
Suppose that you want to find the objects with the smallest values in some (potentially very large) search space.
- First, we need to impose a tree structure satisfying the properties mentioned above. Say that lies in the subtree of if lies above (or is equal to) in the tree.
- Let the "root" be the object of smallest value. Every object must lie in the subtree of the root.
- The children of the root should partition the entire search space (aside from the root) into a bounded number of disjoint subspaces.
- Of course, each child should also have the smallest value in its subtree.
Essentially, we start with the entire search space and then we fracture it into subspaces based on the children of the root. Then we can finish with either of the two approaches.
-th Smallest Spanning Tree (USACO Camp 2018)
Let's look at an example.
Problem
Given a graph with vertices and at most edges, find the -th () smallest spanning tree.
Solution
For this problem, the objects are spanning trees. The root is the minimum spanning tree (which can be calculated with Kruskal's algorithm), and contains all objects in its subtree.
The idea is to designate a small number of children of the root, each of which should be formed by modifying the root slightly. If we can somehow ensure that each object has at most "children" then we only need to consider spanning trees in order to find the -th smallest.
The first step is to consider the easier problem of finding the second MST. To do this, we can choose to exclude one edge of the MST and then find the smallest possible replacement for it. Let the edges in the MST be labeled . Then one idea is to let the -th child subspace of the root to consist of all spanning trees not including edge of the minimum spanning tree for each .
Unfortunately, this doesn't work because the child subspaces overlap. We can instead let the -th child subspace contain all spanning trees that
- include the first edges of the MST
- do not include the -th edge of the MST
for each . Every spanning tree other than the root is contained within exactly one of these child subspaces, which is what we want. After sorting the edges in increasing order of weight once, we can compute the MST within each child subspace in time with DSU.
Overall, the runtime is for storing the information about each spanning tree and for maintaing the priority queue of objects so that we can extract the minimum. Note that with the second approach mentioned in the first section the running time would instead be , which may be too slow.
#include <bits/stdc++.h>using namespace std;typedef bitset<1225> B;typedef vector<int> vi;struct DSU { // for Kruskal'svi e;void init(int n) { e = vi(n, -1); }int get(int x) { return e[x] < 0 ? x : e[x] = get(e[x]); }bool sameSet(int a, int b) { return get(a) == get(b); }
Robotic Cow Herd
Focus Problem – try your best to solve this problem before continuing!
As with the analysis, for each location you should
- sort the controllers of that location by cost
- add the controller of minimum cost for each location to the cost of the cheapest robot
- subtract that minimum cost from every controller at that location (so now the minimum cost controller for each location is just zero)
Importantly, we should then sort the locations by their respective second-minimum controller costs.
Approach 1
Binary search on the cost of the -th robot. If we can compute the costs
of all robots with cost at most or say that there are more than in
time, then we can solve this problem in
time (similar to "Approach 2" above). This
is the approach that the first analysis solution takes, although it includes an
extra factor due to upper_bound
. I have removed this in my solution
below.
#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef vector<int> vi;typedef pair<ll, ll> pl;#define f first#define s second
Approach 2
There's also an time solution with a priority queue that constructs the robots in increasing order of cost. As before, we want each robot to have a bounded number of "child" robots. However, if you look at my DFS function above, it seems that every robot can have up to children! Nevertheless, the DFS takes rather than time due to the break statement, which works since we sorted by second-cheapest robot.
In fact, we can modify the DFS function so that every robot has at most three rather than children.
void dfs(int pos, ll cur, int id) {if (cur > mx || num == K) return;res += cur;num++;if (id + 1 < v[pos].size()) dfs(pos, cur + v[pos][id + 1] - v[pos][id], id + 1);if (pos + 1 < v.size()) {if (id == 1) dfs(pos + 1, cur - v[pos][1] + v[pos + 1][1], 1);if (id) dfs(pos + 1, cur + v[pos + 1][1], 1);}}
Now I'll describe how the priority queue solution works:
First start with the robot of minimum cost. The robot with second-minimum cost can be formed by just choosing the second-minimum controller for the first location. After this, we have a few options:
- We can choose the third-minimum controller for the first location.
- We can discard the second-minimum controller for the first location and select the second-minimum controller for the second location (and never again change the controller selected for the first location).
- We can keep the second-minimum controller for the first location and select the second-minimum controller for the second location (and never again change the controller selected for the first location).
None of these options can result in a robot of lower cost. In general, suppose that we have a robot and are currently selecting the -th cheapest controller for the -th location. Then the transitions are as follows:
- Select the -th cheapest controller for the -th location instead.
- If , select the -st cheapest controller for the -th location instead and also select the -nd cheapest controller for the -st.
- Keep the -th cheapest controller for the -th location and also select the -nd cheapest controller for the -st.
Since there exists exactly one way to get from the cheapest robot to every possible robot, we can use a priority queue.
#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<int, int> pi;typedef vector<int> vi;typedef pair<ll, pi> T;#define f first#define s second
Other Problems
Status | Source | Problem Name | Difficulty | Tags | |
---|---|---|---|---|---|
Baltic OI | Normal | ||||
CCO | Very Hard | ||||
YS | Insane |
Module Progress:
Join the USACO Forum!
Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!