#include <bits/stdc++.h>

using i32 = int_fast32_t;
using i64 = int_fast64_t;
using u32 = uint_fast32_t;
using u64 = uint_fast64_t;
using f64 = double;
using f80 = long double;

#define FOR(v, a, b) for(i64 v = (a); v < (b); ++v)
#define FORE(v, a, b) for(i64 v = (a); v <= (b); ++v)
#define REP(v, n) FOR(v, 0, n)
#define REPE(v, n) FORE(v, 0, n)
#define REV(v, a, b) for(i64 v = (a); v >= (b); --v)
#define ALL(x) (x).begin(), (x).end()
#define RALL(x) (x).rbegin(), (x).rend()
#define ITR(it, c) for(auto it = (c).begin(); it != (c).end(); ++it)
#define RITR(it, c) for(auto it = (c).rbegin(); it != (c).rend(); ++it)
#define EXIST(c,x) ((c).find(x) != (c).end())
#define fst first
#define snd second
#define UNIQ(v) (v).erase(unique(ALL(v)), (v).end())
#define bit(i) (1LL<<(i))

#ifdef DEBUG
#include <Mylib/Debug/debug.cpp>
#else
#define dump(...) ((void)0)
#endif

using namespace std;

template <typename I> void join(ostream &ost, I s, I t, string d=" "){for(auto i=s; i!=t; ++i){if(i!=s)ost<<d; ost<<*i;}ost<<endl;}
template <typename T> istream& operator>>(istream &is, vector<T> &v){for(auto &a : v) is >> a; return is;}
template <typename T> void pout(const T &value){std::cout << value << "\n";}
template <typename T, typename ...Args> void pout(const T &value, const Args&... args){std::cout << value << " ";pout(args...);}

template <typename T, typename U> bool chmin(T &a, const U &b){return (a>b ? a=b, true : false);}
template <typename T, typename U> bool chmax(T &a, const U &b){return (a<b ? a=b, true : false);}
template <typename T, size_t N, typename U> void fill_array(T (&a)[N], const U &v){fill((U*)a, (U*)(a+N), v);}
template <typename T> auto make_vector(int n, int m, const T &value){return vector<vector<T>>(n, vector<T>(m, value));}


struct Init{
  Init(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(12);
    cerr << fixed << setprecision(12);
  }
}init;

template <typename T>
class Compressor{

public:
  std::vector<T> data;
  auto& add(const T &val) {data.push_back(val); return *this;}
  auto& add(const std::vector<T> &vals) {data.insert(data.end(), vals.begin(), vals.end()); return *this;}
  template <typename U, typename ...Args> auto& add(const U &val, const Args&... args) {add(val); return add(args...);}

  auto& build(){
    std::sort(data.begin(), data.end());
    data.erase(std::unique(data.begin(), data.end()), data.end());
    return *this;
  }

  int get_index(const T &val) const {return std::lower_bound(data.begin(), data.end(), val) - data.begin();}

  auto& compress(std::vector<T> &vals) const {for(auto &x : vals) x = get_index(x); return *this;}
  auto& compress(T &val) const {val = get_index(val);return *this;}
  template <typename U, typename ...Args> auto& compress(U &val, Args&... args) const {compress(val); return compress(args...);}

  auto& decompress(std::vector<T> &vals) const {for(auto &x : vals) x = data[x]; return *this;}
  auto& decompress(T &val) const {val = data[val]; return *this;}
  template <typename U, typename ...Args> auto& decompress(U &val, Args&... args) const {decompress(val); return decompress(args...);}
  
  int size() const {return data.size();}
  T operator[](int index) const {return data[index];}
};


struct Point{
  int x, y;
  Point(): x(0), y(0){}
  Point(int x, int y): x(x), y(y){}
  Point& operator+=(const Point &a){this->x += a.x; this->y += a.y; return *this;}
  Point& operator-=(const Point &a){this->x -= a.x; this->y -= a.y; return *this;}
};

Point operator+(const Point &a, const Point &b){return Point(a.x+b.x, a.y+b.y);}
Point operator-(const Point &a, const Point &b){return Point(a.x-b.x, a.y-b.y);}
bool operator==(const Point &a, const Point &b){return a.x == b.x and a.y == b.y;}
bool operator!=(const Point &a, const Point &b){return !(a == b);}

bool operator<(const Point &a, const Point &b){
  return std::make_pair(a.x, a.y) < std::make_pair(b.x, b.y);
}

std::ostream& operator<<(std::ostream &os, const Point &a){
  os << "(" << a.x << "," << a.y << ")";
  return os;
}

namespace grid{
  const auto LEFT = Point(0, -1);
  const auto RIGHT = Point(0, 1);
  const auto UP = Point(-1, 0);
  const auto DOWN = Point(1, 0);
  
  const std::array<Point, 4> dir4 = {LEFT, RIGHT, UP, DOWN};
  const std::array<Point, 8> dir8 = {LEFT, RIGHT, UP, DOWN, LEFT + UP, LEFT + DOWN, RIGHT + UP, RIGHT + DOWN};
}


template <typename Directions, typename Checker>
std::vector<std::vector<int>> grid_bfs(
  const int H, const int W,
  const std::vector<Point> &start_points,
  const Directions &dir,
  const Checker &check_passable
){
  std::vector<std::vector<int>> dist(H, std::vector<int>(W, -1));
  std::vector<std::vector<bool>> visited(H, std::vector<bool>(W));

  std::queue<Point> q;
  for(auto &p : start_points){
    dist[p.x][p.y] = 0;
    q.push(p);
  }
  
  while(not q.empty()){
    auto cur = q.front(); q.pop();

    if(visited[cur.x][cur.y]) continue;
    visited[cur.x][cur.y] = true;

    for(auto &d : dir){
      auto nxt = cur + d;

      if(nxt.x < 0 or nxt.x >= H or nxt.y < 0 or nxt.y >= W or not check_passable(cur, nxt)) continue;

      if(dist[nxt.x][nxt.y] == -1){
        dist[nxt.x][nxt.y] = dist[cur.x][cur.y] + 1;
        q.push(nxt);
      }else{
        if(dist[nxt.x][nxt.y] > dist[cur.x][cur.y] + 1){
          dist[nxt.x][nxt.y] = dist[cur.x][cur.y] + 1;
          q.push(nxt);
        }
      }
    }
  }

  return dist;
}



const i64 INF = 1LL << 40;


struct Cell{
  bool left = 0, right = 0, up = 0, down = 0;
  Cell(){}

  void show(){
    cerr << left << right << up<< down << " ";
  }
};



int main(){
  int N, M;

  while(cin >> N >> M){
    vector<i64> A(N), B(N), C(N);
    vector<i64> D(M), E(M), F(M);

    REP(i,N) cin >> A[i] >> B[i] >> C[i];
    REP(i,M) cin >> D[i] >> E[i] >> F[i];

    Compressor<i64> c_x, c_y;
    c_x.add(A, B, D, 0LL, INF, -INF).build().compress(A, B, D);
    c_y.add(C, E, F, 0LL, INF, -INF).build().compress(C, E, F);

    const int X = c_x.size() - 1;
    const int Y = c_y.size() - 1;


    auto g = make_vector<Cell>(X, Y, Cell());


    REP(i,N){
      FOR(j,A[i],B[i]){
        g[j][C[i]-1].right = true;
        g[j][C[i]].left = true;
      }
    }

    REP(i,M){
      FOR(j,E[i],F[i]){
        g[D[i]][j].up = true;
        g[D[i]-1][j].down = true;
      }
    }
    /*
    REP(i,X){
      REP(j,Y){
        g[i][j].show();
      }
      cerr << endl;
    }
    */
    auto res = grid_bfs(
      X,
      Y,
      {Point(c_x.get_index(0), c_y.get_index(0))},
      grid::dir4,
      [&](const auto &from, const auto &to){
        const auto diff = to - from;

        return
          (diff == grid::LEFT and not g[from.x][from.y].left) or
          (diff == grid::RIGHT and not g[from.x][from.y].right) or
          (diff == grid::UP and not g[from.x][from.y].up) or
          (diff == grid::DOWN and not g[from.x][from.y].down);
      }
    );


    i64 ans = 0;
    bool ok = true;

    REP(i,X){
      dump(res[i]);
    }

    //dump(c_x.data);

    REP(i,X){
      REP(j,Y){
        if(i == 0 or i == X - 1 or j == 0 or j == Y - 1){
          if(res[i][j] != -1){
            ok = false;
          }
        }else{
          if(res[i][j] != -1){
            i64 dx = c_x[i+1] - c_x[i];
            i64 dy = c_y[j+1] - c_y[j];
            //dump(make_tuple(i, j, dx, dy));
            ans += dx * dy;
          }
        }
      }
    }

    
    

    if(ok){
      pout(ans);
    }else{
      pout("INF");
    }
  }

  return 0;
}
