// Complexitate: O(n * m^2), unde n = nr. noduri, m = nr. muchii
#include <bits/stdc++.h>
using namespace std;
ifstream fin("cuplaj.in");
ofstream fout("cuplaj.out");
int sizeLeft; // nr. de noduri din partea stanga a grafului bipartit
int sizeRight; // nr. de noduri din partea dreapta a grafului bipartit
int edges; // nr. total de muchii
int source, sink; // nodurile sursa si destinatie
vector <pair <int, int>> parent; // perechi de tip <nod, indexul muchiei> care retin nodul din care s-a ajuns la nodul curent si indexul muchiei care a fost folosita
vector <pair <int, int >> sinkEdges; // perechi de tip <nod, indexul muchiei> care reprezinta muchiile care conecteaza nodurile din dreapta grafului cu nodul destinatie
vector <vector <pair <int, pair <int, int> >>> adjList; // perechi de tip <nod, <capacitate, indexul muchiei catre nodul vecin>> care reprezinta muchiile grafului
vector <bool> rightNodes; // vector pentru a retine nodurile din dreapta grafului bipartit conectate la cel putin un nod din partea stanga
void createEdge(int x, int y) {
// muchie de la nodul x la nodul y cu capacitate 1 si indexul muchiei
adjList[x].push_back({y, {1,adjList[y].size()}});
// muchie de la nodul y la nodul x cu capacitate 0 si indexul muchiei
adjList[y].push_back({x, {0,adjList[x].size() - 1}});
// daca nodul y este nodul destinatie, adaugam muchia la lista de muchii care conecteaza nodurile din partea dreapta a grafului bipartit la nodul destinatie
if (y == sink) {
sinkEdges.push_back(make_pair(x, adjList[x].size() - 1));
}
}
// Functie care face BFS pe graf si returneaza true / false daca exista sau nu drum de la sursa la destinatie
bool bfs() {
vector <bool> visited(sink + 1);
queue <int> q;
q.push(source);
visited[source] = true;
parent[source] = {-1, -1};
while (!q.empty()) {
int firstInQueue = q.front();
q.pop();
if (firstInQueue == sink) {
return true;
}
int edgeIndex = 0;
for (auto const& neighbor : adjList[firstInQueue]) {
int node = neighbor.first; // nodul vecin
int capacity = neighbor.second.first; // capacitatea muchiei
if (!visited[node] && capacity) {
parent[node] = {firstInQueue, edgeIndex};
visited[node] = true;
q.push(node);
} // daca vecinul n-a fost vizitat si exista capacitate pe muchie, se seteaza parintele nodului vecin si indexul muchiei curente, apoi acesta se adauga in coada
++edgeIndex;
}
}
return visited[sink];
}
int Edmonds_Karp() {
int maxFlow = 0;
while (bfs()) {
for (auto const& neighbor : sinkEdges) { // se parcurge lista de muchii care conecteaza nodurile din partea dreapta a grafului bipartit la nodul destinatie
int currentFlow = 1;
parent[sink] = neighbor; // se seteaza parintele nodului destinatie la nodul curent
int y = sink;
while (parent[y].first != -1) { // se parcurge invers drumul de la nodul destinatie la sursa folosindu-ne de parintele fiecarui nod
int x = parent[y].first;
int i = parent[y].second;
currentFlow = min(currentFlow, adjList[x][i].second.first); // se calculeaza fluxul curent ca fiind minimul dintre fluxul curent si capacitatea muchiei
y = x;
}
// se actualizeaza fluxul pe muchii, parcurgand din nou drumul de la nodul destinatie la sursa
y = sink;
while (parent[y].first != -1) {
int a = parent[y].first;
int b = parent[y].second;
int c = adjList[a][b].second.second;
adjList[a][b].second.first -= currentFlow;
adjList[y][c].second.first += currentFlow;
y = a;
}
maxFlow += currentFlow;
}
}
return maxFlow;
}
int main() {
fin >> sizeLeft >> sizeRight >> edges;
// source = 0;
sink = sizeLeft + sizeRight + 1;
parent.resize(sink + 1);
adjList.resize(sink + 1);
rightNodes.resize(sizeRight + 1);
for (int i = 1; i <= sizeLeft; ++i) {
createEdge(source, i); // legam nodul sursa 0 de toate nodurile din partea stanga a grafului bipartit
}
for (int i = 1; i <= edges; ++i) {
int x, y;
fin >> x >> y;
createEdge(x, sizeLeft + y); // legam nodurile din partea stanga a grafului bipartit cu nodurile din partea dreapta a grafului bipartit
rightNodes[y] = true;
}
for (int i = 1; i <= sizeRight; ++i) {
if (rightNodes[i]) {
createEdge(sizeLeft + i, sink); // legam nodurile din partea dreapta a grafului bipartit cu nodul destinatie
}
}
fout << Edmonds_Karp();
// pentru fiecare nod din partea stanga a grafului bipartit cautam nodul din dreapta la care este conectat
for (int node = 1; node <= sizeLeft; ++node) {
for (auto const &neighbor : adjList[node]) {
int vertex = neighbor.first;
int capacity = neighbor.second.first;
if (vertex && capacity == 0 && vertex != sink) { // daca nodul vecin nu este nodul sursa / destinatie si capacitatea muchiei este 0, inseamna ca nodul curent este conectat la nodul vecin
fout << '\n' << node << " " << vertex - sizeLeft;
}
}
}
fin.close();
fout.close();
return 0;
}
//// cuplaj maxim cu flux
//// val(f) = |M|
//// complexitate: O(mn)
//
//
//#include <iostream>
//#include <fstream>
//#include <vector>
//#include <queue>
//using namespace std;
//
//struct muchie{
// int start,end,cp,ind; // nod1, nod2, capacitate, indice
//};
//int noduri;
//int n,m,e;
//int sursa, destinatie;
//vector<muchie> muchii;
//vector<vector<int>> adj_list;
//vector<int> parinti; // pt nodul x retinem indicele muchiei cu care s-a ajuns la x
//vector<bool> viz;
//
//void adauga_muchie(int a, int b){
// int dim = (int)muchii.size();
//
// adj_list[a].push_back(dim);
// adj_list[b].push_back(dim + 1);
//
// muchii.push_back({a,b, 1, dim + 1});
// muchii.push_back({b,a, 0, dim});
//
//}
//
//bool gaseste_drum(){
// viz.clear();
// parinti.clear();
// viz.resize(noduri, false);
// parinti.resize(noduri);
//
// queue<int> q;
// viz[sursa] = true;
// q.push(sursa);
// while(!q.empty()){
// int node = q.front();
// q.pop();
// if(node != destinatie){
// for(int x : adj_list[node]){ // x este indicele muchiei
// muchie curr_m = muchii[x];
// if(!viz[curr_m.end] and curr_m.cp != 0){
// parinti[curr_m.end] = x;
// q.push(curr_m.end);
// viz[curr_m.end] = true;
// }
// }
// }
// }
// return viz[destinatie];
//}
//
//int main() {
// ifstream fin("cuplaj.in");
// ofstream fout("cuplaj.out");
// fin >> n >> m >> e;
//
// noduri = m + n + 2;
// sursa = 0;
// destinatie = n + m + 1;
// adj_list.resize(noduri);
// while(e--){
// int x,y;
// fin >> x >> y;
// adauga_muchie(x, y + n);
// }
//
// for(int i = 1;i <= n; ++i){
// adauga_muchie(sursa,i);
// }
// for(int j = n + 1; j <= n + m; ++j) {
// adauga_muchie(j,destinatie);
// }
//
// int ans = 0;
// while(gaseste_drum()){ // daca s-a ajuns de la sursa la destinatie
// for(int x : adj_list[destinatie]){ //parcurg invers
// if(viz[muchii[x].end] and muchii[muchii[x].ind].cp != 0){ //daca s-a ajuns cu muchia respectiva in T
// //muchie reziduala // si daca capacitatea nu e 0
// muchie curr_m = muchii[x];
// //indicele muchiei din graf
// parinti[destinatie] = curr_m.ind;
// int flux = 99999999;
// int node = destinatie;
// while(node != sursa){ //flux min
// flux = min(flux,muchii[parinti[node]].cp);
// node = muchii[parinti[node]].start;
// }
//
// node = destinatie;
// while(node != sursa){
// muchii[parinti[node]].cp -= flux;
// muchii[muchii[parinti[node]].ind].cp += flux;
// node = muchii[parinti[node]].start;
// }
// ans += flux;
// }
// }
// }
// fout << ans <<"\n";
// for(auto x: muchii){
// if(x.start < x.end and x.start != sursa and x.end != destinatie and x.cp == 0){
// fout << x.start << " " << x.end-n <<"\n";
// }
// }
// return 0;
//}