USACO 1399: Test Tubes

(Silver)

Algorithm & Visualization

Topic: Constructive Algorithms, Greedy, Stack

Problem Background & Goal

  • Input: $N$ (liquid count), $P$ (partial points), liquid sequences for Tube $A, B$.
  • Tools: Tube A, Tube B, Beaker C (initially empty).
  • Operation: Pour liquid from the top of one container into another.
  • Goal: Tube A contains only liquid 1, Tube B contains only liquid 2 (or empty, and vice versa).
Tube A Tube B

Core Step 1: Dedup

Think: If a tube has 111221, is pouring the top 1 the same operation as pouring all three 1s?

Yes! As long as they are consecutive and the same color, they can be poured at once.

1
2
2
1
1
1
Raw Data
$\Rightarrow$
1
2
1
After Dedup

void dedup(vector &t, char *s) {
    t.clear();
    for (int i = 0; i < n; i++) {
        if (i == 0 || t[t.size()-1] != s[i]-'1')
            t.push_back(s[i]-'1');
    }
}
                    

Core Step 2: Stack & Greedy

Treat tubes as Stacks A, B, and the Beaker as C.

1. Standard Case: Match & Merge

If Top(A) == Top(B): Pour from the taller stack to the shorter one (merge to reduce).

2. Use Beaker (C)

If C is empty: Move the top of the taller stack (A or B) into C.

3. Beaker Matching

If Top(A) == Top(C) or Top(B) == Top(C): Pour matching top into C.

Core Step 3: Edge Cases & Cleanup

Key Observation: Beaker C is only filled when empty, so C's size is always $\le 1$.

Case A: Dual Single Finish

If Size(A)==1 & Size(B)==1 & tops diff colors:
$\to$ Pour C (if any) to matching tube.
$\to$ Done!

Case B: Single Empty Handling

If A is empty (same for B):
1. If Size(B) == 1: Pour C to A, done.
2. Else: Pour B's top to A (split), continue loop.

Algorithm Simulation

Assume A: [1, 2, 2] -> [1, 2] (Top: 2), B: [2, 1] (Top: 1)
2
1
A
1
2
B
C

State: Top(A)=2 != Top(B)=1. C empty.
Action: Move B(1) to C.

2
1
A
2
B
1
C

State: Top(A)=2 == Top(B)=2.
Action: A taller than B, pour A(2) to B (merge).

1
A
2
B
1
C

State: A, B both have 1 layer left & diff colors.
Action: Pour C(1) back to A.

1
A
2
B
C

Done! A is all 1s, B is all 2s.

Core Code: Main Loop (1)


// Strategy 1: Same top color, merge
if (!ts[0].empty() && !ts[1].empty() && ts[0].back() == ts[1].back()) {
    // Greedy: Pour taller tube into shorter one to reduce layers
    if (ts[0].size() > ts[1].size()) {
        ts[0].pop_back();
        ans.push_back({1, 2});
    } else {
        ts[1].pop_back();
        ans.push_back({2, 1});
    }
    continue;
}
                

Core Code: Handle Empty Tube (2)


// Edge Case B: Move to empty tube
// If a tube is empty, and the other has >1 layer, or that layer doesn't belong
if (ts[0].empty() && ts[1].size() > 1 && ts[1].back() != beaker ||
    ts[1].empty() && ts[0].size() > 1 && ts[0].back() != beaker) {
    
    int i = ts[0].empty() ? 0 : 1; // Target is empty tube i
    ts[i].push_back(ts[1-i].back());
    ts[1-i].pop_back();
    ans.push_back({(1-i)+1, i+1});
}
                

Core Code: Cleanup & Beaker (3)


// Edge Case A: End check & Beaker refill
if (ts[0].size() <= 1 && ts[1].size() <= 1) {
    if (beaker != -1) {
        // Beaker not empty, pour back to matching tube
        // If T0 is empty or matches beaker, pour to T0
        int i = (ts[0].empty() || ts[0].back() == beaker) ? 0 : 1;
        ans.push_back({3, i+1});
    }
    break; // Done
}

// Strategy 2/3: Borrow/Fill Beaker
int i;
if (beaker == -1) i = ts[0].size() > ts[1].size() ? 0 : 1; // Beaker empty, take from taller
else i = ts[0].back() == beaker ? 0 : 1; // Beaker not empty, take matching
beaker = ts[i].back();
ts[i].pop_back();
ans.push_back({i+1, 3});
                

Full AC Code


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

int t,n,p,m;
char s1[100005], s2[100005];
vector<int> ts[2];
using pi = pair<int,int>;
vector<pi> ans;
int beaker;

void dedup(vector<int> &t, char *s) {
    t.clear();
    for (int i = 0; i < n; i++) {
        if (i == 0 || t[t.size()-1] != s[i]-'1')
            t.push_back(s[i]-'1');
    }
}

int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%d %d", &n, &p);
        scanf("%s", s1);
        scanf("%s", s2);
        // dedup the colors
        dedup(ts[0], s1);
        dedup(ts[1], s2);
        beaker = -1;
        ans.clear();

        while (1) {
            // merge the same color to the lower tube
            if (!ts[0].empty() && !ts[1].empty() && ts[0].back() == ts[1].back()) {
                if (ts[0].size() > ts[1].size()) {
                    ts[0].pop_back();
                    ans.push_back({1, 2});
                } else {
                    ts[1].pop_back();
                    ans.push_back({2, 1});
                }
                continue;
            }
            // move to an empty tube
            if (ts[0].empty() && ts[1].size() > 1 && ts[1].back() != beaker ||
                ts[1].empty() && ts[0].size() > 1 && ts[0].back() != beaker) {
                int i = ts[0].empty() ? 0 : 1;
                ts[i].push_back(ts[1-i].back());
                ts[1-i].pop_back();
                ans.push_back({(1-i)+1, i+1});
            }
            // done if both have <= 1, move beaker liquid back if necessary
            if (ts[0].size() <= 1 && ts[1].size() <= 1) {
                if (beaker != -1) {
                    int i = (ts[0].empty() || ts[0].back() == beaker) ? 0 : 1;
                    ans.push_back({3, i+1});
                }
                break;
            }
            // move liquid from tube to beaker
            int i;
            if (beaker == -1)
                i = ts[0].size() > ts[1].size() ? 0 : 1;
            else
                i = ts[0].back() == beaker ? 0 : 1;
            beaker = ts[i].back();
            ts[i].pop_back();
            ans.push_back({i+1, 3});
        }
        printf("%d\n", (int)ans.size());
        if (p > 1) {
            for (auto &a: ans)
                printf("%d %d\n", a.first, a.second);
        }
    }
    return 0;
}
                    

Summary

  • Run-Length Encoding (Dedup): Greatly simplifies state space, key to solution.
  • Constructive Algorithm: No search/DP needed, just clear priority logic.
  • Observation: Beaker C only needs capacity of 1 for maneuvering.
  • Complexity: Each op reduces a layer or moves liquid, Linear $O(N)$.
Thanks for watching!