public static ProjectParams fromStdIn() throws IOException {
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    int numTeams = Integer.parseInt(in.readLine());
    // System.out.println("Reading in " + numTeams + " teams...");

    List<Team> teams = new ArrayList<Team>(numTeams);
    List<Set<Employee>> employees = new ArrayList<Set<Employee>>(NUM_LOCATIONS);
    employees.add(new HashSet<Employee>());
    employees.add(new HashSet<Employee>());

    String currLine = in.readLine();

    // Could use while on readLine(), but this handles empty lines at the end better
    for (int currTeam = 0; currTeam < numTeams; currTeam++) {
      String[] idStrings = currLine.split(" ");
      assert (idStrings.length == 2);

      Employee e1 = Employee.getEmployeeFromId(Integer.parseInt(idStrings[0]));
      Employee e2 = Employee.getEmployeeFromId(Integer.parseInt(idStrings[1]));
      assert (e1 != e2 && !e1.equals(e2));
      employees.get(0).add(e1);
      employees.get(1).add(e2);

      teams.add(new Team(e1, e2));

      currLine = in.readLine();
    }
    in.close();

    assert (teams.size() == uniqueTeams(teams));

    return new ProjectParams(teams, employees);
  }
  private void biasForFriend() {
    Vertex f = Vertex.vertexFromEmployee(Employee.getEmployeeFromId(ProjectParams.FRIEND_ID));

    List<Vertex> neighbors = f.getNeighbors();
    for (Vertex v : neighbors) {
      if (flowsForward(v, f)) {
        v.moveToFrontOfNeighbors(f);
      } else {
        v.moveToBackOfNeighbors(f);
      }
    }
  }
class Solver {
  private static final int SOURCE_ID = 1;
  private static final int SINK_ID = 3000;

  private ProjectParams pp;

  private LinkedList<Vertex> V = new LinkedList<Vertex>();
  private Set<Vertex> LV = new HashSet<Vertex>();
  private Set<Vertex> RV = new HashSet<Vertex>();
  private Map<Vertex, Map<Vertex, Integer>> capacity;
  private Map<Vertex, Map<Vertex, Integer>> flow;
  private final Vertex source = Vertex.vertexFromEmployee(Employee.getEmployeeFromId(SOURCE_ID));
  private final Vertex sink = Vertex.vertexFromEmployee(Employee.getEmployeeFromId(SINK_ID));
  private final Vertex friend =
      Vertex.vertexFromEmployee(Employee.getEmployeeFromId(ProjectParams.FRIEND_ID));
  private Set<Vertex> matches = new HashSet<Vertex>();
  private Set<Integer> invitees = new HashSet<Integer>();

  public Solver(ProjectParams pp) {
    this.pp = pp;

    initialize();
  }

  public void solve() {
    relabelToFront();
    // System.out.println("\n" + toString());

    computeMatchSet();
    computeInvitees();
  }

  private void initialize() {
    Set<Employee> Lset = pp.getEmployees(0);
    Set<Employee> Rset = pp.getEmployees(1);

    // FIXME!!!
    // flow = new HashMap<Vertex, Map<Vertex, Integer>>(2 * (Lset.size() + Rset.size()));
    // capacity = new HashMap<Vertex, Map<Vertex, Integer>>(2 * (Lset.size() + Rset.size()));
    flow = new TreeMap<Vertex, Map<Vertex, Integer>>();
    capacity = new TreeMap<Vertex, Map<Vertex, Integer>>();

    setupSourceSide(Lset);
    setupSinkSide(Rset);

    setupLRNeighbors();
    biasForFriend();

    source.setHeight(V.size() + 2);
  }

  private void setupSourceSide(Set<Employee> emps) {
    for (Employee e : emps) {
      Vertex u = Vertex.vertexFromEmployee(e);
      LV.add(u);

      source.addToNeighbors(u);
      u.addToNeighbors(source);

      setCapacity(source, u, 1);
      setFlow(source, u, 1);
      u.setExcess(1);

      // Prioritize friend for initial flow
      if (e.getId() == ProjectParams.FRIEND_ID) {
        V.addFirst(u);
      } else {
        V.add(u);
      }
    }

    source.setExcess(source.getExcess() - source.getNumNeighbors());
  }

  private void setupSinkSide(Set<Employee> emps) {
    for (Employee e : emps) {
      Vertex u = Vertex.vertexFromEmployee(e);
      RV.add(u);

      sink.addToNeighbors(u);
      u.addToNeighbors(sink);

      setCapacity(u, sink, 1);
      setFlow(u, sink, 0);
      V.add(u);
    }
  }

  private void setupLRNeighbors() {
    List<Team> teams = pp.getTeams();
    for (Team t : teams) {
      // System.out.println(t);
      Vertex u = Vertex.vertexFromEmployee(t.getEmployee(0));
      Vertex v = Vertex.vertexFromEmployee(t.getEmployee(1));

      u.addToNeighbors(v);
      v.addToNeighbors(u);

      setCapacity(u, v, 1);
      setFlow(u, v, 0);
    }
  }

  private void biasForFriend() {
    Vertex f = Vertex.vertexFromEmployee(Employee.getEmployeeFromId(ProjectParams.FRIEND_ID));

    List<Vertex> neighbors = f.getNeighbors();
    for (Vertex v : neighbors) {
      if (flowsForward(v, f)) {
        v.moveToFrontOfNeighbors(f);
      } else {
        v.moveToBackOfNeighbors(f);
      }
    }
  }

  private void relabelToFront() {
    assert (!V.isEmpty()) : "No vertices";
    ListIterator<Vertex> iter = V.listIterator();
    while (iter.hasNext()) {
      Vertex v = iter.next();
      // System.out.println("Considering vertex: " + v);
      int oldHeight = v.getHeight();
      discharge(v);
      if (v.getHeight() > oldHeight) {
        iter.remove();
        V.addFirst(v);
        iter = V.listIterator(1);
      }
    }
  }

  // Uses Konig's theorem to compute min vertex cover
  // Compute in both directions as the friend, may or may not appear in a
  // particular vertex cover, depending on graph structure
  private void computeInvitees() {
    Set<Vertex> result;
    Set<Vertex> minVertexCovers1 = computeMinVertexCover(LV, RV);
    Set<Vertex> minVertexCovers2 = computeMinVertexCover(RV, LV);

    if (minVertexCovers1.contains(friend)) {
      result = minVertexCovers1;
    } else {
      result = minVertexCovers2;
    }

    for (Vertex v : result) {
      invitees.add(v.getId());
    }
  }

  private Set<Vertex> computeMinVertexCover(Set<Vertex> side1, Set<Vertex> side2) {
    Set<Vertex> konigSet = new HashSet<Vertex>();
    Set<Vertex> unmatched = new TreeSet<Vertex>(side1);
    unmatched.removeAll(matches);
    // System.out.println("Matches: " + matches);
    // System.out.println("side 1 unmatched set: " + unmatched);

    for (Vertex v : unmatched) {
      konigDFS(konigSet, v, false);
    }

    // System.out.println("Konig set: " + konigSet);

    Set<Vertex> result = new HashSet<Vertex>(side2);
    result.retainAll(konigSet);
    // System.out.println("side 2 intersect konigSet: " + result);

    Set<Vertex> side1notInKonigSet = new HashSet<Vertex>(side1);
    side1notInKonigSet.removeAll(konigSet);
    // System.out.println("side 1 not in Konig set: " + side1notInKonigSet);

    result.addAll(side1notInKonigSet);

    return result;
  }

  private void konigDFS(Set<Vertex> konigSet, Vertex v, boolean edgesInMatch) {
    if (!konigSet.contains(v)) {
      konigSet.add(v);

      for (Vertex neighb : v.getNeighbors()) {
        if (neighb.getId() != SOURCE_ID && neighb.getId() != SINK_ID) {
          if (edgesInMatch == (getFlow(v, neighb) > 0 || getFlow(neighb, v) > 0)) {
            konigDFS(konigSet, neighb, !edgesInMatch);
          }
        }
      }
    }
  }

  private void computeMatchSet() {
    for (Vertex v : LV) {
      for (Vertex neighb : v.getNeighbors()) {
        if (neighb.getId() != SOURCE_ID && getFlow(v, neighb) > 0) {
          matches.add(v);
          matches.add(neighb);
        }
      }
    }
  }

  // Push-relabel methods
  // Push() pushes flow forwards, or decreases incoming flow
  private void push(Vertex u, Vertex v) {
    // System.out.println("Pushing from " + u + " to " + v);
    int excess = u.getExcess();
    int residualCapacity = getResidualCapacity(u, v);

    assert (excess > 0) : "Excess <= 0";
    assert (residualCapacity > 0) : "Resid capacity <= 0";
    assert (u.getHeight() == v.getHeight() + 1) : "Height of u != height of v + 1";

    int changeInFlow = excess < residualCapacity ? excess : residualCapacity;

    if (flowsForward(u, v)) {
      addToFlow(u, v, changeInFlow);
    } else {
      addToFlow(v, u, -changeInFlow);
    }

    u.addToExcess(-changeInFlow);
    v.addToExcess(changeInFlow);
  }

  private void relabel(Vertex u) {
    // System.out.println("Relabeling " + u);
    assert (u.getExcess() > 0) : "u not overflowing";

    List<Vertex> neighbors = u.getNeighbors();
    int minHeight = Integer.MAX_VALUE;
    for (Vertex v : neighbors) {
      int residCapacity = getResidualCapacity(u, v);
      assert (residCapacity == 0 || u.getHeight() <= v.getHeight());
      if (residCapacity > 0) {
        int partnerHeight = v.getHeight();
        minHeight = partnerHeight < minHeight ? partnerHeight : minHeight;
      }
    }
    u.setHeight(1 + minHeight);
  }

  private void discharge(Vertex u) {
    // System.out.println("Discharging " + u);
    while (u.getExcess() > 0) {
      if (u.getCurrNeighbor() >= u.getNumNeighbors()) {
        relabel(u);
        u.setCurrNeighbor(0);
      } else {
        Vertex v = u.getNeighbor(u.getCurrNeighbor());

        if (getResidualCapacity(u, v) > 0 && u.getHeight() == v.getHeight() + 1) {
          push(u, v);
        } else {
          u.incNextNeighbor();
        }
      }
    }
  }

  private int getResidualCapacity(Vertex u, Vertex v) {
    int forwardCapacity = getCapacity(u, v);
    int backwardCapacity = getCapacity(v, u);

    // u->v
    if (forwardCapacity > 0) {
      return getCapacity(u, v) - getFlow(u, v);
    }
    // v->u
    else if (backwardCapacity > 0) {
      return getFlow(v, u);
    }

    return 0;
  }

  private boolean flowsForward(Vertex u, Vertex v) {
    return getCapacity(u, v) > 0;
  }

  private int getFlow(Vertex u, Vertex v) {
    return getEdgeInfo(u, v, flow, 0);
  }

  private int getCapacity(Vertex u, Vertex v) {
    return getEdgeInfo(u, v, capacity, 0);
  }

  private int getEdgeInfo(
      Vertex u, Vertex v, Map<Vertex, Map<Vertex, Integer>> edgeInfo, int nullResult) {
    Map<Vertex, Integer> edges = edgeInfo.get(u);
    if (edges == null) {
      return nullResult;
    } else {
      Integer result = edges.get(v);
      if (result == null) {
        return nullResult;
      }
      return result;
    }
  }

  private void setFlow(Vertex u, Vertex v, int newFlow) {
    setEdgeInfo(u, v, flow, newFlow);
  }

  private void setCapacity(Vertex u, Vertex v, int newCapacity) {
    setEdgeInfo(u, v, capacity, newCapacity);
  }

  private void setEdgeInfo(
      Vertex u, Vertex v, Map<Vertex, Map<Vertex, Integer>> edgeInfo, int newVal) {
    Map<Vertex, Integer> edges = edgeInfo.get(u);
    if (edges == null) {
      edges = new HashMap<Vertex, Integer>();
      edges.put(v, newVal);
      edgeInfo.put(u, edges);
    } else {
      edges.put(v, newVal);
    }
  }

  private void addToFlow(Vertex u, Vertex v, int flowInc) {
    addToEdgeInfo(u, v, flow, flowInc);
  }

  private void addToCapacity(Vertex u, Vertex v, int capInc) {
    addToEdgeInfo(u, v, capacity, capInc);
  }

  private void addToEdgeInfo(
      Vertex u, Vertex v, Map<Vertex, Map<Vertex, Integer>> edgeInfo, int incVal) {
    Map<Vertex, Integer> edges = edgeInfo.get(u);
    assert (edges != null);
    // if(edges == null){
    // 	edges = new HashMap<Vertex, Integer>();
    // 	edges.put(v, incVal);
    // 	edgeInfo.put(u, edges);
    // }
    // else{
    edges.put(v, incVal + edges.get(v));
    // }
  }

  public void printSolution() {
    System.out.println(invitees.size());
    for (Integer inviteeId : invitees) {
      System.out.println(inviteeId);
    }
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("Source:\t").append(source).append("\n");
    sb.append("Sink:\t").append(sink).append("\n");
    sb.append("Vertex list:\n").append(V).append("\n");
    sb.append("Capacity:\n").append(capacity).append("\n\n");
    // sb.append("Flow:\n").append(flow).append("\n");
    sb.append("Flow:\n");
    for (Map.Entry<Vertex, Map<Vertex, Integer>> edges : flow.entrySet()) {
      Integer id1 = edges.getKey().getId();
      sb.append(id1).append(" -> ");
      for (Map.Entry<Vertex, Integer> flowEdge : edges.getValue().entrySet()) {
        if (flowEdge.getValue() > 0) {
          Integer id2 = flowEdge.getKey().getId();
          sb.append(id2).append(": ").append(flowEdge.getValue()).append("\t");
        }
      }
      sb.append("\n");
    }

    return sb.toString();
  }
}