//------------------------------------------------------------
#define MOD 1000000007

//------------------------------------------------------------
#if 1
#include <bits/stdc++.h>
using namespace std;

//------------------------------------------------------------
using ll = long long;
#define int ll

#define FOR(i, s, e) for (ll i = ll(s); i < (ll)e; ++i)
#define RFOR(i, s, e) for (ll i = ll(e) - 1; i >= ll(s); --i)
#define REP(i, n) for (ll i = 0, i##_size = n; i < i##_size; ++i)
#define RREP(i, n) for (ll i = ll(n) - 1; i >= 0; --i)

//------------------------------------------------------------
template <class T>
struct arr : public vector<T> {
	arr() {}
	arr(ll n, T v = T()) : vector<T>(n, v) {}
	void init(ll n, T v = T()) {
		this->clear();
		this->resize(n, v);
	}
	ll sz() const { return (ll)this->size(); }
	void pb(T v) { this->push_back(v); }
	void sort() { std::sort(this->begin(), this->end()); }
	void rsort() { std::sort(this->begin(), this->end(), greater<T>()); }
	void uniqueErase() {
		this->erase(std::unique(this->begin(), this->end()), this->end());
	}
};
using ints = arr<ll>;
using intss = arr<arr<ll>>;

template <class T>
struct que : public queue<T> {
	ll sz() const { return (ll)this->size(); }
	T popfront() {
		T v = this->front();
		this->pop();
		return v;
	}
};

template <class A, class B>
struct pr {
	union {
		A a;
		A key;
		A first;
		A x;
	};
	union {
		B b;
		B value;
		B second;
		B y;
	};

	pr() : a(A()), b(B()) {};
	pr(A a_, B b_) : a(a_), b(b_) {}
	pr(pr const& r) : a(r.a), b(r.b) {};
	pr(pair<A, B> const& r) : a(r.first), b(r.second) {};

	bool operator == (pr const& r) const {
		return a == r.a && b == r.b;
	}
	bool operator != (pr const& r) const {
		return !((*this) == r);
	}
	bool operator < (pr const& r) const {
		if (a == r.a) {
			return b < r.b;
		}
		return a < r.a;
	}

	pr operator + (pr v) const { return pr(x, y) += v; }
	pr operator - (pr v) const { return pr(x, y) -= v; }

	pr& operator += (pr v) {
		x += v.x;
		y += v.y;
		return *this;
	}
	pr& operator -= (pr v) {
		x -= v.x;
		y -= v.y;
		return *this;
	}

	friend istream& operator>>(istream& is, pr& p) {
		is >> p.a >> p.b;
		return is;
	}
};
using pint = pr<ll, ll>;
using pints = arr<pint>;

template <class K, class V>
struct mp : public map<K, V> {
	bool get(K const& k, V* v) {
		auto it = this->find(k);
		if (it != this->end()) {
			*v = it->second;
			return true;
		}
		return false;
	}
};

template <class T>
struct arr2 {
	vector<vector<T>> vec;

	arr2() {}
	arr2(int w, int h, T const& value = T()) { vec.resize(h, vector<T>(w, value)); }
	arr2(arr2 const& r) { vec = r.vec; }
	arr2(arr2&& r) { vec = move(r.vec); }
	arr2& operator=(arr2 const& r) { vec = r.vec; return *this; }
	arr2& operator=(arr2&& r) { vec = move(r.vec); return *this; }

	int width() const {
		return (int)vec[0].size();
	}
	int height() const {
		return (int)vec.size();
	}

	void init(int w, int h, T const& value = T()) {
		vec.clear();
		vec.resize(h, vector<T>(w, value));
	}
	T& operator()(int x, int y) { return vec.at(y).at(x); }
	T const& operator()(int x, int y) const { return vec.at(y).at(x); }

	T& operator()(pint p) { return vec.at(p.y).at(p.x); }
	T const& operator()(pint p) const { return vec.at(p.y).at(p.x); }
};

//------------------------------------------------------------
#if defined(TEST)
extern istream& myInputStream;
extern ostream& myOutputStream;
#else
istream& myInputStream = cin;
ostream& myOutputStream = cout;
#endif

struct input {
	template <class A, class B>
	void exec(pair<A, B>& var) {
		myInputStream >> var.first >> var.second;
	}

	template <class T>
	void exec(T& var) {
		myInputStream >> var;
	}

	template <class T>
	operator T() {
		T var;
		exec(var);
		return var;
	}
} input;

struct inputs {
	ll n;
	inputs(ll n_) : n(n_) {}

	template <class T>
	operator arr<T>() {
		arr<T> var(n);
		REP(i, n) {
			var[i] = (T)input;
		};
		return var;
	}
};

template <class T>
void output(T var) {
	myOutputStream << (var) << '\n';
}

//------------------------------------------------------------
template <class T> void chmin(T& a, T b) { if (b < a) { a = b; } }
template <class T> void chmax(T& a, T b) { if (b > a) { a = b; } }

constexpr int gcd(int a, int b) {
	if (a < 0) { a = -a; }
	if (b < 0) { b = -b; }
	if (a == 0) { return b; }
	if (b == 0) { return a; }

	while (int c = a % b) {
		a = b;
		b = c;
	}
	return b;
}

//------------------------------------------------------------
struct bfs {
	ints froms;
	ints steps;

	bfs(int N, intss const& adjacency) {
		froms.init(N, -1);
		steps.init(N, -1);

		que<ll> queue;
		queue.push(0);
		froms[0] = 0;
		steps[0] = 0;

		while (queue.empty() == false) {
			ll p = queue.popfront();

			for (ll n : adjacency[p]) {
				if (froms[n] != -1) {
					continue;
				}

				froms[n] = p;
				steps[n] = steps[p] + 1;
				queue.push(n);
			}
		}
	}
};

//------------------------------------------------------------
template <int M>
struct modint {
	int raw;

	modint() { raw = 0; }
	modint(int v) {
		if (v < 0) {
			raw = (v % M) + M;
		}
		else if (v >= M) {
			raw = v % M;
		}
		else {
			raw = v;
		}
	}
	modint operator + (modint v) const { return modint(raw) += v; }
	modint operator - (modint v) const { return modint(raw) -= v; }
	modint operator * (modint v) const { return modint(raw) *= v; }

	modint& operator += (modint v) {
		raw += v.raw;
		if (raw >= M) { raw -= M; }
		return *this;
	}
	modint& operator -= (modint v) {
		raw -= v.raw;
		if (raw < 0) { raw += M; }
		return *this;
	}
	modint& operator *= (modint v) {
		raw = (raw * v.raw) % M;
		return *this;
	}
	modint pow(int n) const {
		return modint::pow(raw, n);
	}
	static modint pow(int a, int n) {
		if (n < 0) {
			// not support
			abort();
		}

		int r = 1;
		while (n) {
			if (n & 1) {
				r = (r * a) % M;
			}
			a = (a * a) % M;
			n >>= 1;
		}
		return modint(r);
	}

	friend istream& operator>>(istream& is, modint& m) {
		int v;
		is >> v;
		m = modint(v);
		return is;
	}
	friend ostream& operator<<(ostream& os, modint const& m) {
		return os << m.raw;
	}
};

using mint = modint<MOD>;
using mints = arr<mint>;

//------------------------------------------------------------
#if defined(TEST)
#include "test.hpp"
#else
void myMain();
signed main() {
	cin.tie(0);
	ios::sync_with_stdio(0);
	cout << fixed << setprecision(16);
	myMain();
	return 0;
}
#endif
#endif

struct HorizontalLine {
	int y;
	int left;
	int right;
};

struct VerticalLine {
	int x;
	int top;
	int bottom;
};

//------------------------------------------------------------
void myMain() {
	int N = input;
	int M = input;

	arr<int> xs;
	arr<int> ys;
	arr<VerticalLine> vls(N);
	arr<HorizontalLine> hls(M);
	REP(i, N) {
		auto& vl = vls[i];
		vl.top = input;
		vl.bottom = input;
		vl.x = input;

		ys.pb(vl.top);
		ys.pb(vl.bottom);
		xs.pb(vl.x);
	}

	REP(i, M) {
		auto& hl = hls[i];
		hl.y = input;
		hl.left = input;
		hl.right = input;

		ys.pb(hl.y);
		xs.pb(hl.left);
		xs.pb(hl.right);
	}

	// 問題と座標系が縦横逆になってる

	// xs/ysは圧縮座標から実座標への変換テーブルになる
	xs.pb(0);
	ys.pb(0);
	xs.sort();
	xs.uniqueErase();
	ys.sort();
	ys.uniqueErase();

	// 実座標から圧縮座標への変換テーブル
	map<int, int> r2ix;
	map<int, int> r2iy;
	REP(i, xs.sz()) {
		r2ix[xs[i]] = i;
	}
	REP(i, ys.sz()) {
		r2iy[ys[i]] = i;
	}

	// bomber座標系に
	arr2<char> field(xs.sz() * 2 - 1, ys.sz() * 2 - 1, 0);
	REP(i, N) {
		if (i >= vls.size()) {
			continue;
		}
		auto const& vl = vls[i];

		if (r2ix.find(vl.x) == r2ix.end()) {
			continue;
		}

		int bx = r2ix[vl.x] * 2;
		if (bx >= field.width()) {
			continue;
		}

		if (r2iy.find(vl.top) == r2iy.end()) {
			continue;
		}
		if (r2iy.find(vl.bottom) == r2iy.end()) {
			continue;
		}
		if (r2iy[vl.top] * 2 >= field.height()) {
			continue;
		}
		if (r2iy[vl.bottom] * 2 >= field.height()) {
			continue;
		}

		for (int by = r2iy[vl.top] * 2; by <= r2iy[vl.bottom] * 2; ++by) {
			field(bx, by) = 1;
		}
	}
	REP(i, M) {
		if (i >= hls.size()) {
			continue;
		}
		auto const& hl = hls[i];

		if (r2iy.find(hl.y) == r2iy.end()) {
			continue;
		}

		int by = r2iy[hl.y] * 2;
		if (by >= field.height()) {
			continue;
		}

		if (r2ix.find(hl.left) == r2ix.end()) {
			continue;
		}
		if (r2ix.find(hl.right) == r2ix.end()) {
			continue;
		}
		if (r2ix[hl.left] * 2 >= field.width()) {
			continue;
		}
		if (r2ix[hl.right] * 2 >= field.width()) {
			continue;
		}

		for (int bx = r2ix[hl.left] * 2; bx <= r2ix[hl.right] * 2; ++bx) {
			field(bx, by) = 1;
		}
	}

	//REP(y, field.height()) {
	//	REP(x, field.width()) {
	//		if (field(x, y)) {
	//			cout << "#";
	//		}
	//		else {
	//			cout << " ";
	//		}
	//	}
	//	cout << endl;
	//}

	bool over = false;

	// bomber座標指定で面積取得
	auto GetSize = [&xs, &ys, &over](int bx, int by) -> int {
		if (bx % 2 == 0 || by % 2 == 0) {
			return 0;
		}
		if (bx % 2 == 0 || by % 2 == 0) {
			return 0;
		}

		int ix = bx / 2;
		int iy = by / 2;

		if (ix + 1>= xs.sz()) {
			over = true;
			return 0;
		}
		if (iy + 1 >= ys.sz()) {
			over = true;
			return 0;
		}

		int w = xs[ix + 1] - xs[ix];
		int h = ys[iy + 1] - ys[iy];
		return w * h;
	};

	int cx = r2ix[0] * 2 + 1;
	int cy = r2iy[0] * 2 + 1;

	que<pint> queue;
	queue.push(pint(cx, cy));
	if (cx < field.width() && cy < field.height()) {
		field(cx, cy) = 1;
	}
	int ans = GetSize(cx, cy);

	while (queue.empty() == false) {
		pint p = queue.popfront();

		for (pint around : {pint(-1, 0), pint(0, -1), pint(1, 0), pint(0, 1)}) {
			pint next = p + around;

			if (next.x < 0 || next.x >= field.width() || 
				next.y < 0 || next.y >= field.height()) {
				over = true;
				break;
			}

			if (field(next) != 0) {
				continue;
			}

			field(next) = 1;
			queue.push(next);
			ans += GetSize(next.x, next.y);
		}
	}

	if (over) {
		output("INF");
	}
	else {
		output(ans);
	}
}



Measured memory_bytes: 39120