#line 1 "/workspaces/compro/lib/template.hpp"


#line 1 "/workspaces/compro/lib/io/vector.hpp"
#include <iostream>
#include <vector>

#ifndef IO_VECTOR
#define IO_VECTOR

template <class T> std::ostream &operator<<(std::ostream &out, const std::vector<T> &v) {
  int size = v.size();
  for (int i = 0; i < size; i++) {
    std::cout << v[i];
    if (i != size - 1)
      std::cout << " ";
  }
  return out;
}

template <class T> std::istream &operator>>(std::istream &in, std::vector<T> &v) {
  for (auto &el : v) {
    std::cin >> el;
  }
  return in;
}

#endif
#line 4 "/workspaces/compro/lib/template.hpp"
#include <bits/stdc++.h>
#define REP(i, n) for (int i = 0; i < n; i++)
#define FOR(i, m, n) for (int i = m; i < n; i++)
#define ALL(v) (v).begin(), (v).end()
#define coutd(n) cout << fixed << setprecision(n)
#define ll long long int
#define vl vector<ll>
#define vi vector<int>
#define MM << " " <<

using namespace std;

template <class T> void say(bool val, T yes, T no) { cout << (val ? yes : no) << "\n"; }

void say(bool val, string yes = "Yes", string no = "No") { say<string>(val, yes, no); }

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

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

// C++ 17に完全移行したら消す
// 最大公約数を求める
template <class T> T gcd(T n, T m) { return n ? gcd(m % n, n) : m; }

// 最小公倍数を求める
template <class T> T lcm(T n, T m) {
  int g = gcd(n, m);
  return n * m / g;
}

// 重複を消す。計算量はO(NlogN)
template <class T> void unique(std::vector<T> &v) {
  std::sort(v.begin(), v.end());
  v.erase(std::unique(v.begin(), v.end()), v.end());
}


#line 2 "main.cpp"

long long solve(int N, const std::vector<long long> &A) {
  int bingo = 0;
  vi ma1(N, -10000000), ma2(N, -10000000);
  vector<vi> dp(N, vi(N, -10000000));
  // 混ざらないように更新分はここで管理。
  vector<vi> tmp(N, vi(N, -10000000));
  dp[A[0]][A[1]] = 0;
  ma1[A[0]] = 0;
  ma2[A[1]] = 0;

  queue<pair<int, int>> que;

  // pが2個、qが1個
  auto same = [&](int p, int q) -> void {
    // pが2個あるので、1個取っておけばスコアが稼げる。
    REP(k, N) {
      que.push({k, q});
      chmax(tmp[k][q], dp[k][p] + 1);
      chmax(tmp[k][q], dp[p][k] + 1);
    }
  };

  FOR(i, 1, N + 1) {
    int p, q = -1, r = -1;
    p = A[3 * i - 1];
    if (i < N) {
      q = A[3 * i];
      r = A[3 * i + 1];
    }

    if (p == q && q == r) {
      bingo++;
      continue;
    } else if (q == -1) {
      // 最後の追加は一つだけ
      dp[p][p]++;
      break;
    } else if (p == q) {
      same(p, r);
    } else if (q == r) {
      same(q, p);
    } else if (r == p) {
      same(r, q);
    }

    int ma = -10000000;
    REP(i, N) {
      chmax(ma, ma1[i]);
      chmax(ma, ma2[i]);
    }

    // pに注目、q,rはこの順で並んでいる必要あり
    auto func = [&](int p, int q, int r) -> void {
      // q,rを残すとき、p pを残していれば1点増える。それ以外はk lは任意なので一番でかいものだと思ってよい
      que.push({q, r});
      chmax(tmp[q][r], dp[p][p] + 1);
      chmax(tmp[q][r], ma);

      // kとpを残す場合、kの片割れが自由になるのでそこだけ全部調べる。
      // qとrが等しい場合の処理は既にtmpに含まれているので考慮不要
      REP(k, N) {
        que.push({k, p});
        chmax(tmp[k][p], ma1[k]);
        chmax(tmp[k][p], ma2[k]);
      }
    };

    func(p, q, r);
    func(q, p, r);
    func(r, p, q);

    // tmpが触ったデータ分だけdpや最大値を更新する。それ以外は書き換えない
    // これがp,q,rを全部捨てた時の更新に当たる
    while (!que.empty()) {
      auto [x, y] = que.front();
      que.pop();
      chmax(ma1[x], tmp[x][y]);
      chmax(ma2[y], tmp[x][y]);
      chmax(dp[x][y], tmp[x][y]);
      tmp[x][y] = -10000000;
    }
  }

  int ans = 0;
  REP(k, N) {
    REP(l, N) { chmax(ans, dp[k][l]); }
  }
  return ans + bingo;
}

// generated by online-judge-template-generator v4.4.0 (https://github.com/kmyk/online-judge-template-generator)
int main() {
  int N;
  std::cin >> N;
  std::vector<long long> A(3 * N);
  for (int i = 0; i < 3 * N; ++i) {
    std::cin >> A[i];
    A[i]--;
  }
  auto ans = solve(N, A);
  std::cout << ans << std::endl;
  return 0;
}


Measured memory_bytes: 35156