USACO Gold 2019 Open - I Would Walk 500 Miles

Authors: Benjamin Qi, Aryansh Shrivastava, Chuyang Wang

Official Analysis (C++)

Solution 1: Prim's Algorithm

For this problem, we want to maximize the minimum of the number of miles two cows are willing to walk, henceforth referred to as MM. Let's consider a Minimum Spanning Tree (MST) of all NN cows. By adding the ii-th edge to the MST, it implies that all edges between the rest NiN - i components have weights higher or equal than this added edge, or, in other words, MM is equal to the weight of this added edge.

In case of splitting the cows into KK groups, we remove the K1K - 1 edges from the MST which have the maximum weight. MM is then equal to the weight of the minimum of those removed edges. If the edges in the MST are sorted in ascending order, MM would be the weight of the NK+1N - K + 1-th edge of the MST.

As the typical implementation of Kruskal's Algorithm requires O(N2logN)\mathcal{O}(N^2 \log N) time for a dense graph as in our case, we use instead Prim's Algorithm with linear search for the closest vertex. Each scan only takes O(N)\mathcal{O}(N) time, and we only have to do NN scans.

Time Complexity: O(N2)\mathcal{O}(N^2)

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 2019201997LL;
const ll FACTOR1 = 2019201913LL;
const ll FACTOR2 = 2019201949LL;
/**
* @return the number of miles cow a + 1 and b + 1 are willing to walk to see

Java

import java.io.*;
import java.util.*;
public class Walk {
static final long MOD = 2019201997L;
static final long FACTOR1 = 2019201913L;
static final long FACTOR2 = 2019201949L;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("walk.in"));

Solution 2: Kruskal's Algorithm & Two-Pass Radix Sort

Alternatively, we can also use Kruskal's Algorithm to construct our MST. Note that only the step of sorting all edges takes too long time. If we manage to sort all edges in linear time, we might be able to solve it with Kruskal's Algorithm as well.

To sort these edges, we can apply a two-pass radix sort. In particular, we first use counting sort to sort the first 16 bits ([0,216)[0, 2^{16})) of the weights. Then, in the second iteration, we sort the rest 16 bits ([216,232)[2^{16}, 2^{32})). After the sorting process, we apply the unmodified Kruskal's Algorithm on the edges and MM is equal to the weight of the NK+1N - K + 1-th longest edge of the MST.

Time Complexity: O(N2)\mathcal{O}(N^2)

Warning: TLE

Some large test cases might get TLE due to the bad constant factor for the radix sort.

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
Code Snippet: DSU (Click to expand)
const ll MOD = 2019201997LL;
const ll FACTOR1 = 2019201913LL;
const ll FACTOR2 = 2019201949LL;

Solution 3: Math

Since this problem has its roots in optimizing a given weight expression, we are motivated to use math.

First, we can use modular arithmetic to find a direct expression for the modular residue. The goal is to find an expression for the modular residue involving only basic operations that we will be able to optimize with mathematical techniques. Using modular arithmetic properties (negative numbers in modular arithmetic), consider the expression (mod2019201997)\pmod{2019201997} to reduce the numbers:

2019201913x+2019201949y84x48y(mod2019201997)2019201913x+2019201949y \equiv - 84 x - 48 y \pmod {2019201997}

Since x,yx,y are bounded by N7500,N \leq 7500, this expression will always give a negative value whose absolute value does not exceed the modulus 2019201997.2019201997. Thus, we can easily find an expression for the modular residue from here if we just shift up by the modulus 20192019972019201997 once:

(2019201913x+2019201949y)mod2019201997=201920199784x48y.(2019201913x+2019201949y) \mod 2019201997 = 2019201997 - 84 x - 48 y.

Now, it remains to optimize this expression. Formally, we must find a KK-partition of the cows such that

minx,yin different groups(201920199784x48y)\min\limits_{x,y \, \text{in different groups}} (2019201997 - 84 x - 48 y)

is maximized.

Now, let's analyze the function's behavior: for large x,y,x,y, the expression becomes small, and for small x,y,x,y, the expression becomes large.

But remembering the condition that x,yx,y must be in different groups to contribute to the answer, this means that large x,yx,y should be together in the same group (to greedily avoid making the expression small) and small x,yx,y should be in different groups (to greedily make the expression large). This leads to the following greedy strategy:

Make one group with all the largest cows that can fit and k1k-1 groups with all the smallest cows. Of course, the k1k-1 smallest cows are just 1k1,1 \dots k-1, so this means that the other n(k1)n-(k-1) cows will be in the large group.

Now that we have pinpointed the optimal grouping, we can find the answer by going back to the question: we want to minimize the expression. To minimize the expression, we should greedily choose the largest cows that are in different groups (since large x,yx,y make the expression smaller). Clearly, this means that we should take cow n,n, the largest cow in the group of large cows, and k1,k-1, the largest cow not in the group of large cows.

Recall that the problem statement requires x<y,x < y, so we have x=k1,y=n.x = k - 1, y = n. Our answer is as easy as a substitution of these values into the modular residue expression 201920199784x48y:2019201997 - 84 x - 48 y:

201920199784(k1)48n2019201997 - 84 (k-1) - 48 n

In retrospect, this solution is equivalent to performing Kruskal's MST algorithm by hand when we analyze the edge weight expression and use greedy logic to create the optimal grouping. (So the previous solutions will find the exact same grouping.)

Time Complexity: O(1)\mathcal{O}(1)

Senpat's implementation:

C++

#include <bits/stdc++.h>
using namespace std;
int main() {
freopen("walk.in", "r", stdin);
freopen("walk.out", "w", stdout);
int N, K;
cin >> N >> K;
long long answer = 2019201997LL - 48LL * N - 84LL * (K - 1LL);
cout << answer << endl;
}

Java

import java.io.*;
import java.util.*;
public class Walk {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("walk.in"));
StringTokenizer st = new StringTokenizer(br.readLine());
br.close();
int N = Integer.parseInt(st.nextToken());

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!