---
title: "Polynomial-Time Reductions"
format:
  html:
    toc: true
    number-sections: true
    code-line-numbers: true
---

[In-class notes](hand_written_notes/Reductions.pdf)

## Learning Goals

- Reduce one problem to another
- Define Polynomial Time Reduction
- Describe why reductions are important

## Cell Tower Transmission Problem

**Input:** 

- Array $P$ such that $P[i]$ is the location of the $i^\textrm{th}$ cell tower.
- Array $D$ such that $D[i]$ is the number of data packets tower $i$ has in its queue.


**Output:**
A set $T$ of towers such that should broadcast in the next time step. Note that if two towers within 2 miles of each other broadcast at the same time, there will be interference of their signals.

We would like to solve the Cell Tower Transmission Problem using a solver that solves MWIS. To do that, we need to turn the input to the Cell Tower Transmission problem into a MWIS input, and the we need to turn the resulting output from the MWIS solver back into a solution to our original problem. This process of converting one type of problem into another and back again is called a reduction (formal definition in next section). Graphically, this would look like:

![Graphical representation of how the Cell Tower problem is converted to the MWIS problem](quarto_note_pdfs/CellTower.png){fig-alt="" #fig-CellToMWIS}


As a group, please discuss:

- What should your input conversion and output conversion from @fig-CellToMWIS be doing (generally), and why?
- The social impact of implementing this Cell Tower transmission algorithm
  - Stakeholders
  - Who benefits
  - Who is disadvantaged
  - Cycles of reinforcement for winners/losers?
- If you implement your conversion functions as algorithms, what are their asymptotic worst-case runtimes in terms of $n$, the number of towers.

## Polynomial-Time Reductions

We can more generally try to solve a problem $P$ using a solver for problem $Q$ by using an input conversion function $f$ and an output conversion $g$, as in
@fig-GenericReduction. 

![Graphical depiction of a generic reduction from problem $P$ to problem $Q$ ](quarto_note_pdfs/generic_reduction.png){fig-alt="A smaller rectangular box labeled Q surrounded by a larger rectangular box labelled P. A small circle labeled f inside the P box to the left of the Q box, and a small circle labeled g inside the P box to the right of the Q box, with arrows pointing from left to right from outside the P box to the f circle, from the f-circle to the Q box, from the Q box to the g-circle, and then from the g circle to outside the P box." #fig-GenericReduction}

However, this picture become a bit non-sensical if, for example, $f=P$. In that case, the input conversion function is doing all of the work, and the solver for $Q$ is not doing any work to solve the problem. This is not really the situation we want to consider. Thus, we consider reductions where the computational power of $f$ and $g$, the input and output conversion functions, are limited. This limitation ensures that the thing that is doing most of the work of solving $P$ is the solver for $Q$.
Formally, we define a polynomial time reduction as:

:::{#def-polyReduction}
If we can solve problem $P$ with a solver for $Q$ such that the runtime of the input and output conversion functions ($f$ and $g$ in @fig-GenericReduction) have polynomial runtimes, we say "$P$ is polynomial time reducible to $Q$," or "$P$ reduces to $Q$," denoted by $P\leq_p Q.$
:::

In the above definition, we use the term "polynomial runtime:"

:::{#def-poly}
We call $O(n^d)$ for a constant $d$ polynomial scaling.
:::

Examples of polynomial runtimes in $n$:

- $O(1)$
- $O(n)$
- $O(n\log n)$
- $O(n^{10})$

Essentially, we have limited the input and output conversion functions in our reduction to *not* be able to do things that take exponential time. From the perspective of exponential scaling, polynomial time scaling is practically nothing, trivial.

### Remembering the direction of reduction

It can be hard to remember which problem is which when we say "$P$ reduces to $Q$," or $P\leq_p Q.$

Let's first try to keep track of what we mean when we write $P\leq_p Q.$ To remember this, note that a solver for $Q$ is so powerful, it can not only solve the problem $Q$, it can also solve the problem $P$ (with only a trivial) amount of extra work done by the input and output conversion functions. Thus a solver for $Q$ is more powerful than a solver for $Q$, and that is why $Q$ is greater than $P$.

When we say "$P$ reduces to $Q$," because the word "reduces" sounds like we are getting smaller, it can sound like the $Q$ solver is less powerful, but this is the opposite of what we are trying to say. One way to remember is to draw a diagram like in @fig-GenericReductionMnemonic, and imagine the $P$ box shrinking down (reducing) to the $Q$ box. But at the same time, keep in mind that even though the $Q$ box is smaller, it is the more powerful solver.

![Mnemonic picture for remembering direction of reduction from $P$ to problem $Q$ ](quarto_note_pdfs/reduction_mnemonic.png){fig-alt="A smaller rectangular box labeled Q surrounded by a larger rectangular box labelled P with arrows pointing from the corners of P to the corners of Q." #fig-GenericReductionMnemonic width=50%}

### Why do we care about reductions

There are two main reasons to care about reductions:

- **Practical:** If we have a good algorithm for $Q$ we can use it to solve $P$ without having to code up an entirely new algorithm from scratch.
- **Conceptual:** Reductions give us a way to compare the difficulty of problems and the resources needed to solve problems. If the resources needed to solve a problem $Q$ can also allow us to solve a problem $P$, then $Q$ must be at least as hard to solve as $P$, if not harder. 
