## Problem Description and Formulation

The company aims to maximize viewership by buying ad space on taxis, buses, and privately owned cars. The costs and expected viewership for each platform are as follows:
- Taxi: $500 per ad, 5000 viewers per ad
- Bus: $1000 per ad, 12000 viewers per ad
- Privately owned car: $300 per ad, 2000 viewers per ad

Constraints:
- The bus company limits the number of ads from a single company to at most 8.
- At most 30% of the ads should be on taxis.
- At least 20% of ads should be on privately owned cars.
- The company has a budget of $20000.

## Decision Variables

Let \(x_t\), \(x_b\), and \(x_c\) represent the number of ads on taxis, buses, and privately owned cars, respectively.

## Objective Function

Maximize the total viewership: \(5000x_t + 12000x_b + 2000x_c\).

## Constraints

1. Budget constraint: \(500x_t + 1000x_b + 300x_c \leq 20000\)
2. Bus ads limit: \(x_b \leq 8\)
3. Taxi ads percentage limit: \(x_t \leq 0.3(x_t + x_b + x_c)\)
4. Privately owned car ads percentage limit: \(x_c \geq 0.2(x_t + x_b + x_c)\)
5. Non-negativity and integer constraints: \(x_t, x_b, x_c \geq 0\) and are integers.

## Gurobi Code

```python
import gurobipy as gp

# Create a new model
m = gp.Model("Ad_Placement")

# Decision variables
x_t = m.addVar(name="taxis", vtype=gp.GRB.INTEGER)  # Ads on taxis
x_b = m.addVar(name="buses", vtype=gp.GRB.INTEGER)  # Ads on buses
x_c = m.addVar(name="cars", vtype=gp.GRB.INTEGER)  # Ads on privately owned cars

# Objective: Maximize viewership
m.setObjective(5000*x_t + 12000*x_b + 2000*x_c, gp.GRB.MAXIMIZE)

# Budget constraint
m.addConstr(500*x_t + 1000*x_b + 300*x_c <= 20000, name="budget")

# Bus ads limit
m.addConstr(x_b <= 8, name="bus_limit")

# Taxi ads percentage limit
m.addConstr(x_t <= 0.3*(x_t + x_b + x_c), name="taxi_percentage")

# Privately owned car ads percentage limit
m.addConstr(x_c >= 0.2*(x_t + x_b + x_c), name="car_percentage")

# Non-negativity
m.addConstr(x_t >= 0, name="taxis_nonneg")
m.addConstr(x_b >= 0, name="buses_nonneg")
m.addConstr(x_c >= 0, name="cars_nonneg")

# Solve the model
m.optimize()

# Print the results
if m.status == gp.GRB.OPTIMAL:
    print("Optimal Solution:")
    print(f"Taxis: {x_t.varValue}")
    print(f"Buses: {x_b.varValue}")
    print(f"Cars: {x_c.varValue}")
    print(f"Max Viewership: {m.objVal}")
else:
    print("No optimal solution found.")
```