using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
if (InputPattern == "Input1") {
WillReturn.Add("2 3 3");
WillReturn.Add("1 1 2 2");
WillReturn.Add("1 2 2 3");
WillReturn.Add("1 1 1 3");
//2
}
else if (InputPattern == "Input2") {
WillReturn.Add("5 5 3");
WillReturn.Add("1 1 5 5");
WillReturn.Add("1 1 4 4");
WillReturn.Add("2 2 3 3");
//3
}
else {
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
}
return WillReturn;
}
static int mX; // Xの頂点数
static int mY; // Yの頂点数
static int mRect; // 長方形の数
static int UB;
// 隣接行列で枝を表現
static int[,] mCapacityArr;
static int[,] mFlowArr;
static void Main()
{
List<string> InputList = GetInputList();
int[] wkArr = { };
Action<string> SplitAct = pStr =>
wkArr = pStr.Split(' ').Select(pX => int.Parse(pX)).ToArray();
SplitAct(InputList[0]);
mY = wkArr[0];
mX = wkArr[1];
mRect = wkArr[2];
var NodeNameList = new List<string>();
for (int I = 1; I <= mY; I++) {
NodeNameList.Add("Y" + I.ToString());
}
for (int I = 1; I <= mX; I++) {
NodeNameList.Add("X" + I.ToString());
}
for (int I = 1; I <= mRect; I++) {
NodeNameList.Add("RectA_" + I.ToString());
NodeNameList.Add("RectB_" + I.ToString());
}
NodeNameList.Add("Source");
NodeNameList.Add("Sink");
// ノードID[ノード名]なDict
var NodeIDDict = new Dictionary<string, int>();
foreach (string EachStr in NodeNameList) {
NodeIDDict[EachStr] = NodeIDDict.Count;
}
UB = NodeIDDict.Count - 1;
mCapacityArr = new int[UB + 1, UB + 1];
mFlowArr = new int[UB + 1, UB + 1];
Action<string, string> AddEdge = (pNodeName1, pNodeName2) =>
{
int FromNode = NodeIDDict[pNodeName1];
int ToNode = NodeIDDict[pNodeName2];
mCapacityArr[FromNode, ToNode] = 1;
};
// グラフに枝を追加する
for (int I = 1; I <= mX; I++) {
AddEdge("Source", "X" + I.ToString());
}
for (int I = 1; I <= mY; I++) {
AddEdge("Y" + I.ToString(), "Sink");
}
int RectNo = 1;
foreach (string EachStr in InputList.Skip(1)) {
SplitAct(EachStr);
int MinY = wkArr[0];
int MaxY = wkArr[2];
int MinX = wkArr[1];
int MaxX = wkArr[3];
string RectA = "RectA_" + RectNo.ToString();
string RectB = "RectB_" + RectNo.ToString();
for (int I = MinX; I <= MaxX; I++) {
AddEdge("X" + I.ToString(), RectA);
}
AddEdge(RectA, RectB);
for (int I = MinY; I <= MaxY; I++) {
AddEdge(RectB, "Y" + I.ToString());
}
RectNo++;
}
// エドモンズ・カープで解く
Solve(NodeIDDict["Source"], NodeIDDict["Sink"]);
}
static void Solve(int pSourceNode, int pSinkNode)
{
while (true) {
List<int> NodeList = ExecBFS(pSourceNode, pSinkNode);
if (NodeList == null) break;
//Console.WriteLine("経路を発見しました");
//NodeList.ForEach(pX => Console.Write("{0},", pX));
//Console.WriteLine();
// 経路に流す量
int CurrFlow = int.MaxValue;
for (int I = 0; I <= NodeList.Count - 2; I++) {
int FromNode = NodeList[I];
int ToNode = NodeList[I + 1];
CurrFlow = Math.Min(CurrFlow, mCapacityArr[FromNode, ToNode]);
}
//Console.WriteLine("この経路に{0}の水を流します", CurrFlow);
for (int I = 0; I <= NodeList.Count - 2; I++) {
int FromNode = NodeList[I];
int ToNode = NodeList[I + 1];
mCapacityArr[FromNode, ToNode] -= CurrFlow;
mFlowArr[FromNode, ToNode] += CurrFlow;
// 逆辺を追加する
mCapacityArr[ToNode, FromNode] += CurrFlow;
}
}
int Answer = 0;
for (int I = 0; I <= UB; I++) {
Answer += mFlowArr[I, UB];
}
Console.WriteLine(Answer);
}
struct JyoutaiDef
{
internal int CurrNode;
internal List<int> NodeList;
}
// 幅優先探索を行い、始点から終点へのノードのListを返す
// なければnullを返す
static List<int> ExecBFS(int pSourceNode, int pSinkNode)
{
var Que = new Queue<JyoutaiDef>();
JyoutaiDef WillEnqueue;
WillEnqueue.CurrNode = pSourceNode; // 始点のノードはmSourceNode
WillEnqueue.NodeList = new List<int>();
WillEnqueue.NodeList.Add(WillEnqueue.CurrNode);
Que.Enqueue(WillEnqueue);
// BFSを繰り返すので、レベルの低い訪問を優先しても問題ない
var VisitedSet = new HashSet<int>();
while (Que.Count > 0) {
JyoutaiDef Dequeued = Que.Dequeue();
// 終点のノードはmSinkNode
if (Dequeued.CurrNode == pSinkNode) {
return Dequeued.NodeList;
}
for (int I = 0; I <= UB; I++) {
int CurrCapacity = mCapacityArr[Dequeued.CurrNode, I];
if (CurrCapacity == 0) continue;
if (VisitedSet.Add(I) == false) continue;
WillEnqueue.CurrNode = I;
WillEnqueue.NodeList = new List<int>(Dequeued.NodeList) { I };
Que.Enqueue(WillEnqueue);
}
}
return null;
}
}