(Analysis by Danny Mittal)

It's first important to observe that for any valid pairing, we can sort the Guernseys in the pairing, then sort the Holsteins in the pairing, and then pair up the first Guernsey with the first Holstein, the second Guernsey with the second Holstein, and so on, and that will also be a valid pairing.

This idea means that we can attain any valid set of paired cows using a DP where the first parameter is how many of the first Guernseys we've already used, and the second parameter is how many of the first Holsteins we've already used, since it's optimal to pair the next Guernsey we pair with the next Holstein we pair from the above idea. We will use this DP to solve the problem.

Subtask 1: $T = 1$, $N \leq 5000$

Minimizing the sums of weights of the unpaired cows is the same as maximizing the sums of weights of paired cows. Furthermore, the pairing of cows with the highest sum of weights will clearly be maximal, because otherwise we could add the pair of unpaired cows to increase the sum of weights. Therefore, this subtask is equivalent to finding the pairing of cows with maximum sum of weight of the paired cows.

We can do this using the exact DP idea explained above. Define $dp_{x, y}$ to be the maximum weight of paired cows where we've only considered the first $x$ Guernseys and the first $y$ Holsteins. There are three ways to transition to a state $(x, y)$: by choosing not to pair the $x$-th Guernsey, by choosing not to pair the $y$-th Holstein, or by pairing up the $x$-th Guernsey with the $y$-th Holstein.

There are $O(N^2)$ states and the transitions can be computed in constant time, so the runtime is $O(N^2)$, which is fast enough for the given constraints.

Code:

import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;

public class PairedUpHarderMin {

public static void main(String[] args) throws IOException {
int t = Integer.parseInt(tokenizer.nextToken());
if (t != 1) {
throw new IllegalArgumentException();
}
int n = Integer.parseInt(tokenizer.nextToken());
int k = Integer.parseInt(tokenizer.nextToken());
int[] guernseyLocations = new int[n + 1];
long[] guernseyWeights = new long[n + 1];
int[] holsteinLocations = new int[n + 1];
long[] holsteinWeights = new long[n + 1];
boolean[] isGuernsey = new boolean[n + 1];
int g = 0;
int h = 0;
long totalImportance = 0;
for (int j = 1; j <= n; j++) {
isGuernsey[j] = tokenizer.nextToken().equals("G");
int location = Integer.parseInt(tokenizer.nextToken());
int weight = Integer.parseInt(tokenizer.nextToken());
if (isGuernsey[j]) {
g++;
guernseyLocations[g] = location;
guernseyWeights[g] = weight;
} else {
h++;
holsteinLocations[h] = location;
holsteinWeights[h] = weight;
}
totalImportance += weight;
}
long[][] dp = new long[g + 1][h + 1];
for (int j1 = 1; j1 <= g; j1++) {
for (int j2 = 1; j2 <= h; j2++) {
dp[j1][j2] = Math.max(dp[j1 - 1][j2], dp[j1][j2 - 1]);
if (Math.abs(guernseyLocations[j1] - holsteinLocations[j2]) <= k) {
dp[j1][j2] = Math.max(dp[j1][j2], dp[j1 - 1][j2 - 1] + guernseyWeights[j1] + holsteinWeights[j2]);
}
}
}
long answer = totalImportance - dp[g][h];
}
}


Subtask 2: $T = 2$, $N \leq 300$

For $T = 2$ we will solve the problem directly. We can use the same DP states as we did for $T = 1$, but when choosing not to pair a Guernsey, we need to make sure that it is more than $K$ distance to the right from the last Holstein we chose not to pair, and vice versa.

To account for this, we could augment our DP state to also include the last Guernsey we chose not to pair and the last Holstein we chose not to pair. This DP has $O(N^4)$ states, and the transitions are essentially the same as before, so $O(1)$. Unfortunately, no subtask had constraints low enough for $O(N^4)$ to pass (though it would pass the samples).

We can improve this by noting that we don't actually need to know the last Holstein and the last Guernsey that we chose not to pair -- we only need to know the last cow that we chose not to pair. This is because if the last cow we chose not to pair was a Guernsey, then it was clearly more than $K$ distance to the right from the last Holstein that we chose not to pair, so it's valid to choose not to pair an additional Guernsey (and vice versa).

Formally, we compute a DP $dp_{x, y, j}$ equal to the maximum sum of weights of unpaired cows where we've considered the first $x$ Guernseys and the first $y$ Holsteins, and the last cow we chose not to pair was cow $j$. This improves the amount of states to $O(N^3)$, while keeping the transitions basically the same. A runtime of $O(N^3)$ is sufficient to pass this subtask.

Ben's code:

#include <bits/stdc++.h>
using namespace std;

template <class T> using V = vector<T>;

int main() {
int T, N, K;
cin >> T >> N >> K;
assert(T == 2);
V<pair<int, int>> cows;
for (int i = 0; i < N; ++i) {
char b;
int x, y;
cin >> b >> x >> y;
cows[b == 'H'].push_back({x, y});
}
const int A = (int)cows.size();
const int B = (int)cows.size();
V<V<V<int>>> dp(A + 1, V<V<int>>(B + 1, V<int>(N + 1, -1)));
// dp[i][j][k] = i Guernseys processed, j Holsteins processed,
// index of last unpaired (or N if none)
dp[N] = 0;
auto ckmax = [&](int &a, int b) { a = max(a, b); };
for (int i = 0; i <= A; ++i)
for (int j = 0; j <= B; ++j)
for (int k = 0; k <= N; ++k) {
if (dp[i][j][k] == -1)
continue;
if (i < A && j < B &&
abs(cows[i].first - cows[j].first) <= K)
ckmax(dp[i + 1][j + 1][k], dp[i][j][k]);
if (i < A)
if (!(A <= k && k < A + B &&
cows.at(i).first <= cows.at(k - A).first + K))
ckmax(dp[i + 1][j][i],
dp[i][j][k] + cows.at(i).second);
if (j < B)
if (!(k < A &&
cows.at(j).first <= cows.at(k).first + K))
ckmax(dp[i][j + 1][A + j],
dp[i][j][k] + cows.at(j).second);
}
int ans = INT_MIN;
for (int i = 0; i <= N; ++i)
ans = max(ans, dp[A][B][i]);
cout << ans;
}


Subtask 3: $T = 2$, $N \leq 5000$

We will solve the final subtask using the same general DP idea as before, but with a slightly different DP than in the previous subtask. Rather than including the last cow that we chose not to pair in the DP state, we will include an indicator variable saying either that we are only allowed to not pair Guernseys right now, or that we are only allowed to not pair Holsteins right now.

Formally, define $dp_{x, y, b}$ to be the maximum sum of weights of unpaired cows where we've considered the first $x$ Guernseys and the first $y$ Holsteins, and the next cow that we choose not to pair must be of breed $b$. This DP has $O(N^2)$ states. The motivation for this DP idea is similar to for the previous subtask: if we choose not to pair a cow of breed $b$, then we are also free not to pair the next cow of breed $b$, or any following cow of that breed until we at some point choose not to pair a cow of the opposite breed.

This means that at a state $(x, y, \text{G})$, choosing not to pair the next Guernsey simply transitions to the state $(x + 1, y, \text{G})$, because we're still free to choose not to pair a Guernsey again. Similarly, from a state $(x, y, \text{H})$, choosing not to pair a Holstein transitions to a state $(x, y + 1, \text{H})$.

Choosing to pair two cows is as simple as before: we simply transition from the state $(x, y, b)$ to the state $(x + 1, y + 1, b)$.

The complication comes when we consider the fact that we don't want to be forced to only not pair Guernseys or only not pair Holsteins. At a state $(x, y, b)$, we may want to start not pairing cows of the other breed. This introduces another transition. Assume that $b = \text{G}$. If we're being forced right now to only not pair Guernseys, then in the worst case the last unpaired cow was the Guernsey we considered, which was the $x$-th Guernsey.

This means that if we want to switch to not pairing Holsteins, then we need to repeatedly pair cows until the next available Holstein is more than $K$ distance to the right from the $x$-th Guernsey. If the next Holstein more than $K$ distance to the right from the $x$-th Guernsey is the $y' + 1$-th Holstein, then we need to pair the next $y' - y$ pairs of Guernseys and Holsteins, arriving at the state $(y' - y + x, y')$.

We can compute this transition in constant time by precomputing $y'$ for each $x$. We also need to make sure that those $y' - y$ pairs are actually valid pairs; this can also be checked in constant time using precomputation.

We also perform a similar transition for the case where $b = \text{H}$. All of the necessary transitions can therefore be computed in constant time, so since we have $O(N^2)$ states, the runtime is $O(N^2)$ which is sufficient to pass the final subtask.

Code:

import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;

public class PairedUpHarderMaxSimpler {
public static final long SMALL = -1000000000;

public static void main(String[] args) throws IOException {
int t = Integer.parseInt(tokenizer.nextToken());
if (t != 2) {
throw new IllegalArgumentException();
}
int n = Integer.parseInt(tokenizer.nextToken());
int k = Integer.parseInt(tokenizer.nextToken());
int[] guernseyLocations = new int[n + 1];
long[] guernseyWeights = new long[n + 1];
int[] holsteinLocations = new int[n + 1];
long[] holsteinWeights = new long[n + 1];
boolean[] isGuernsey = new boolean[n + 1];
int g = 0;
int h = 0;
for (int j = 1; j <= n; j++) {
isGuernsey[j] = tokenizer.nextToken().equals("G");
int location = Integer.parseInt(tokenizer.nextToken());
int weight = Integer.parseInt(tokenizer.nextToken());
if (isGuernsey[j]) {
g++;
guernseyLocations[g] = location;
guernseyWeights[g] = weight;
} else {
h++;
holsteinLocations[h] = location;
holsteinWeights[h] = weight;
}
}

int[][] lastValidPairStartingFrom = new int[g + 1][h + 1];
for (int x = g; x >= 0; x--) {
for (int y = h; y >= 0; y--) {
if (x < g && y < h && Math.abs(guernseyLocations[x + 1] - holsteinLocations[y + 1]) <= k) {
lastValidPairStartingFrom[x][y] = lastValidPairStartingFrom[x + 1][y + 1];
} else {
lastValidPairStartingFrom[x][y] = x;
}
}
}
int[] lastTooCloseHolstein = new int[g + 1];
for (int x = 1; x <= g; x++) {
while (lastTooCloseHolstein[x] <= h && holsteinLocations[lastTooCloseHolstein[x]] <= guernseyLocations[x] + k) {
lastTooCloseHolstein[x]++;
}
lastTooCloseHolstein[x]--;
}
int[] lastTooCloseGuernsey = new int[h + 1];
for (int y = 1; y <= h; y++) {
while (lastTooCloseGuernsey[y] <= g && guernseyLocations[lastTooCloseGuernsey[y]] <= holsteinLocations[y] + k) {
lastTooCloseGuernsey[y]++;
}
lastTooCloseGuernsey[y]--;
}
long[][][] dp = new long[g + 1][h + 1];
for (int b = 0; b <= 1; b++) {
for (int x = 0; x <= g; x++) {
for (int y = 0; y <= h; y++) {
dp[b][x][y] = SMALL;
}
}
}
dp = 0;
dp = 0;
for (int x = 0; x <= g; x++) {
for (int y = 0; y <= h; y++) {
int nextY = Math.max(y, lastTooCloseHolstein[x]);
if (lastValidPairStartingFrom[x][y] - x + y >= nextY) {
dp[nextY - y + x][nextY] = Math.max(dp[nextY - y + x][nextY], dp[x][y]);
}
int nextX = Math.max(x, lastTooCloseGuernsey[y]);
if (lastValidPairStartingFrom[x][y] >= nextX) {
dp[nextX][nextX - x + y] = Math.max(dp[nextX][nextX - x + y], dp[x][y]);
}

if (lastValidPairStartingFrom[x][y] >= x + 1) {
dp[x + 1][y + 1] = Math.max(dp[x + 1][y + 1], dp[x][y]);
dp[x + 1][y + 1] = Math.max(dp[x + 1][y + 1], dp[x][y]);
}

if (x < g) {
dp[x + 1][y] = Math.max(dp[x + 1][y], dp[x][y] + guernseyWeights[x + 1]);
}
if (y < h) {
dp[x][y + 1] = Math.max(dp[x][y + 1], dp[x][y] + holsteinWeights[y + 1]);
}
}
}
System.out.println(Math.max(dp[g][h], dp[g][h]));
}
}


Open(?) Problem: Can you solve $T=1$ in $o(N^2)$ time?

Author's Note: This was inspired by a task from 21M.387. Given a song, a list of estimated boundary locations, and a list of ground truth boundary locations, we can define the number of "true positives" to be the maximum number of $(\text{estimated location}, \text{ground truth location})$ pairs one can form such that $\text{estimated location}$ is within $\tau$ seconds of $\text{ground truth location}$ for some choice of $\tau$.