# 1.18 Reductions

A reduction transforms one problem into another problem. It is a way to reuse algorithms and transfer difficulty. If problem A can be converted into problem B, and we already know how to solve B, then we can solve A by translating the input, running the solver for B, and translating the output back.

Reductions are useful in two directions. For algorithm design, they let you solve a new problem with a known algorithm. For lower bounds and hardness, they show that a problem is at least as hard as another problem.

## Problem

You have a problem that resembles a known problem, but the connection is not exact. You need to make the connection precise so that an existing algorithm or lower bound applies.

## Solution

Use a reduction template:

```text
solve_A(input_A):
  input_B = transform_A_to_B(input_A)
  output_B = solve_B(input_B)
  output_A = transform_B_to_A(output_B)
  return output_A
```

A valid reduction must preserve correctness:

```text
input_A has answer yes if and only if input_B has answer yes
```

For optimization problems, it must preserve the objective value or provide a controlled way to recover the objective for the original problem.

## Example: Two Sum as Set Lookup

Problem A:

```text
Given an array A and target t, decide whether two distinct elements sum to t.
```

Known problem B:

```text
Set membership:
Given a set S and value x, decide whether x is in S.
```

Reduction idea:

While scanning the array, ask whether the complement `t - A[i]` has already appeared.

```text
two_sum(A, t):
  seen = empty set

  for x in A:
    if t - x in seen:
      return true
    add x to seen

  return false
```

The original pair-sum problem is reduced to repeated set membership queries.

Correctness depends on the order of operations. We check the complement before inserting `x`, so the algorithm never uses the same occurrence twice.

## Example: Longest Common Subsequence as Dynamic Programming

Problem A:

```text
Given strings X and Y, find the length of their longest common subsequence.
```

Reduction idea:

Convert the original problem into subproblems over prefixes.

State:

```text
dp[i][j] = LCS length of X[0..i-1] and Y[0..j-1]
```

The problem is reduced to a table of smaller prefix problems. The final answer is `dp[n][m]`.

This is a reduction within the same problem family. Many dynamic programming algorithms can be understood this way: a large instance is reduced to smaller indexed instances with known dependencies.

## Example: Bipartite Matching as Max Flow

Problem A:

```text
Given a bipartite graph with left side L and right side R,
find the maximum number of disjoint matched pairs.
```

Known problem B:

```text
Maximum flow.
```

Construct a flow network:

```text
source -> each vertex in L        capacity 1
each edge L -> R                  capacity 1
each vertex in R -> sink          capacity 1
```

Every unit of flow corresponds to one matched pair. The capacity constraints ensure that each vertex is used at most once. Therefore a maximum flow gives a maximum matching.

This reduction lets us use any correct max-flow algorithm to solve bipartite matching.

## Reductions for Lower Bounds

Reductions can also show that a problem is difficult.

Suppose problem A is known to require at least `Omega(n log n)` time in a model. If we can reduce A to problem B efficiently, then a very fast algorithm for B would imply a very fast algorithm for A. If that contradicts the lower bound for A, then B must also be hard.

The direction matters:

```text
A reduces to B
```

means:

```text
A can be solved using a solver for B.
```

So B is at least as hard as A, up to the cost of the transformation.

## Reduction Checklist

A reduction should answer these questions:

| Question                            | Purpose                                        |
| ----------------------------------- | ---------------------------------------------- |
| What is the source problem?         | The problem you want to solve or transfer from |
| What is the target problem?         | The problem you already know how to solve      |
| How is the input transformed?       | Builds an instance of the target problem       |
| How is the output transformed back? | Recovers an answer to the source problem       |
| Why is correctness preserved?       | Proves the translation is faithful             |
| What is the transformation cost?    | Determines the final complexity                |

## Complexity Accounting

If the transformation from A to B costs `O(f(n))`, and the solver for B costs `O(g(n))`, then the total cost is usually:

```text
O(f(n) + g(n))
```

When the transformed instance has a different size, state that explicitly.

Example:

```text
The graph construction creates n + 2 vertices and m + 2n edges.
```

Then the max-flow complexity should be expressed in terms of those new parameters.

## Common Pitfalls

Do not reduce in the wrong direction. If you want to solve A using B, you need a transformation from A instances to B instances.

Do not ignore the cost of transformation. A reduction that creates a huge instance may destroy the intended efficiency.

Do not assume similar-looking problems reduce cleanly. The transformation must preserve all constraints.

Do not lose witnesses. If the original problem asks for an actual solution, the reduction must show how to recover it from the target output.

## Takeaway

A reduction is a correctness-preserving translation between problems. It lets you reuse known algorithms, organize dynamic programming states, and transfer lower bounds. A good reduction states the source problem, target problem, input transformation, output recovery, correctness argument, and total complexity.

