Problem:
Score : 600 points

Problem Statement

We have 3N cards arranged in a row from left to right, where each card has an integer between 1 and N (inclusive) written on it. The integer written on the i-th card from the left is A_i.

You will do the following operation N-1 times:

Rearrange the five leftmost cards in any order you like, then remove the three leftmost cards. If the integers written on those three cards are all equal, you gain 1 point.

After these N-1 operations, if the integers written on the remaining three cards are all equal, you will gain 1 additional point.

Find the maximum number of points you can gain.

Constraints

1 \leq N \leq 2000

1 \leq A_i \leq N

Input

Input is given from Standard Input in the following format:

N
A_1 A_2 \cdots A_{3N}

Output

Print the maximum number of points you can gain.

Sample Input 1

2
1 2 1 2 2 1

Sample Output 1

2

Let us rearrange the five leftmost cards so that the integers written on the six cards will be 2\ 2\ 2\ 1\ 1\ 1 from left to right.

Then, remove the three leftmost cards, all of which have the same integer 2, gaining 1 point.

Now, the integers written on the remaining cards are 1\ 1\ 1.

Since these three cards have the same integer 1, we gain 1 more point.

In this way, we can gain 2 points - which is the maximum possible.

Sample Input 2

3
1 1 2 2 3 3 3 2 1

Sample Output 2

1

Sample Input 3

3
1 1 2 2 2 3 3 3 1

Sample Output 3

3

Solution in C# (.NET Core 3.1.201) (judge verdict: Wrong Answer)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pair = System.Tuple<int,int>;


class TEST{
	static void Main(){
		Sol mySol =new Sol();
		mySol.Solve();
	}
}

class Sol{
	void ChMax(ref int x, int v){
		x = Math.Max(x, v);
	}
	int score(int x, int y, int z){ return (x == y && y == z) ? 1 : 0;}
	public void Solve(){
		
		int Inf = (int) 1e9;
		int[][] dp = new int[N + 1][];
		for(int i=0;i<N+1;i++){
			dp[i] = new int[N + 1];
			for(int j=0;j<N+1;j++){
				dp[i][j] = -Inf;
			}
		}
		
		Action<int, int, int> update = (r, c, v) =>{
			ChMax(ref dp[r][c], v);
			ChMax(ref dp[c][r], v);
			ChMax(ref dp[r][N], v);
			ChMax(ref dp[c][N], v);
			ChMax(ref dp[N][c], v);
			ChMax(ref dp[N][r], v);
			ChMax(ref dp[N][N], v);
		};
		
		update(A[0], A[1], 0);
		
		int offset = 0;
		for(int i=0;i<N-1;i++){
			int a = A[2 + i * 3], b = A[3 + i * 3], c = A[4 + i * 3];
			// X, Y, a, b, c から3つ取るうち， (a, b, c)を取る以外を考える．
			// 　例えば X,b,cを取ると残りは Y,aなので，
			// 　配るdp的には   dp[Y][a] <- Max{ dp[X][Y] + score(X, b, c), dp[Y][a] } で更新する．
			// 　遷移先が (a,b,c) を取るものを除けば 高々O(N) 個しかないので，
			// 　遷移先から貰うdpに変えて高速化できないか考える．⇒O(1)で貰えるようにできる
			
			// Flying tableが使えないので，更新候補をキューに詰めておいて一気に更新する．
			
			if(score(a, b, c) == 1){
				offset++;
				continue;
			}
			
			var q = new Queue<long>();
			const long M = 2048L, MM = M * M, MMM = MM * 2;
			Func<int, int, int, long> enc = (v, x, y) => Math.Abs(v) * MMM + (v < 0 ? 1 : 0) * MM + x * M + y;
			
			// (X, Y, a, b, c) => (X, a)
			// (Y,b,c)を取って(X,a) にする
			// 各 X = x に対して，すべてのY = y in [0..N-1] が (x,a) に遷移する
			// ・dp[x][N] := max(dp[x][y]) for y から score 0で dp[x][a] に遷移
			// ・dp[x][Y=b], dp[x][Y=c]からは score(Y, b, c) が1になる可能性があるので個別に遷移させておく
			
			// case: (X, Y, a, b, c) => (X, a)
			for(int x = 0; x < N; x++){
				if(dp[x][N] >= 0) q.Enqueue(enc(dp[x][N], x, a));
				if(b == c) if(dp[x][b] >= 0) q.Enqueue(enc(dp[x][b] + score(b, b, c), x, a));
				if(b == c) if(dp[x][c] >= 0) q.Enqueue(enc(dp[x][c] + score(c, b, c), x, a));
			}
			
			// case:  (X, Y, a, b, c) => (X, b)
			for(int x = 0; x < N; x++){
				if(dp[x][N] >= 0) q.Enqueue(enc(dp[x][N], x, b));
				if(a == c) if(dp[x][a] >= 0) q.Enqueue(enc(dp[x][a] + score(a, a, c), x, b));
				if(a == c) if(dp[x][c] >= 0) q.Enqueue(enc(dp[x][c] + score(c, a, c), x, b));
			}
			
			// case:  (X, Y, a, b, c) => (X, c)
			for(int x = 0; x < N; x++){
				if(dp[x][N] >= 0) q.Enqueue(enc(dp[x][N], x, c));
				if(a == b) if(dp[x][a] >= 0) q.Enqueue(enc(dp[x][a] + score(a, a, b), x, c));
				if(a == b) if(dp[x][b] >= 0) q.Enqueue(enc(dp[x][b] + score(b, a, b), x, c));
			}
			
			// case:  (X, Y, a, b, c) => (Y, a)
			for(int y = 0; y < N; y++){
				if(dp[N][y] >= 0) q.Enqueue(enc(dp[N][y], y, a));
				if(b == c) if(dp[b][y] >= 0) q.Enqueue(enc(dp[b][y] + score(b, b, c), y, a));
				if(b == c) if(dp[c][y] >= 0) q.Enqueue(enc(dp[c][y] + score(c, b, c), y, a));
			}
			// case:  (X, Y, a, b, c) => (Y, b)
			for(int y = 0; y < N; y++){
				if(dp[N][y] >= 0) q.Enqueue(enc(dp[N][y], y, b));
				if(a == c) if(dp[a][y] >= 0) q.Enqueue(enc(dp[a][y] + score(a, a, c), y, b));
				if(a == c) if(dp[c][y] >= 0) q.Enqueue(enc(dp[c][y] + score(c, a, c), y, b));
			}
			// case:  (X, Y, a, b, c) => (Y, c)
			for(int y = 0; y < N; y++){
				if(dp[N][y] >= 0) q.Enqueue(enc(dp[N][y], y, c));
				if(a == b) if(dp[a][y] >= 0) q.Enqueue(enc(dp[a][y] + score(a, a, b), y, c));
				if(a == b) if(dp[b][y] >= 0) q.Enqueue(enc(dp[b][y] + score(b, a, b), y, c));
			}
			// (X, Y, a, b, c) => (a, b)
			// すべての X = x，すべてのY = y  が (a,b) に遷移する
			// ・dp[N][N] := max(dp[x][y]) for x,y から score 0で dp[a][b] に遷移
			// ・dp[c][c] からは score(X, Y, c) が1になる可能性があるので個別に遷移させておく
			
			// case:  (X, Y, a, b, c) => (a, b)
			q.Enqueue(enc(dp[N][N], a, b));
			if(dp[c][c] >= 0) q.Enqueue(enc(dp[c][c] + score(c, c, c), a, b));
			
			// case:  (X, Y, a, b, c) => (a, c)
			q.Enqueue(enc(dp[N][N], a, c));
			if(dp[b][b] >= 0) q.Enqueue(enc(dp[b][b] + score(b, b, b), a, c));
			
			// case:  (X, Y, a, b, c) => (b, c)
			q.Enqueue(enc(dp[N][N], b, c));
			if(dp[b][b] >= 0) q.Enqueue(enc(dp[a][a] + score(a, a, a), b, c));
			
//Console.WriteLine("ok");			
			
			// Update
			while(q.Count > 0){
				var e = q.Dequeue();
				int val = (int)(e / MMM);
				int sig = (int)(e % MMM / MM);
				int x = (int)(e % MM / M);
				int y = (int)(e % M);
//Console.WriteLine("{0} {1} {2}", val, x, y);
				if(sig == 0){
					dp[x][y] = Math.Max(dp[x][y], val);
					dp[y][x] = Math.Max(dp[y][x], val);
					dp[x][N] = Math.Max(dp[x][N], val);
					dp[y][N] = Math.Max(dp[y][N], val);
					dp[N][x] = Math.Max(dp[N][x], val);
					dp[N][y] = Math.Max(dp[N][y], val);
					dp[N][N] = Math.Max(dp[N][N], val);
				}// update(x, y, val);
			}
		}
		
		for(int i=0;i<=N;i++){
			for(int j=0;j<=N;j++){
				dp[i][j] += offset;
			}
		}
		int ans = -Inf;
		for(int i=0;i<N;i++){
			for(int j=0;j<N;j++){
				ChMax(ref ans, dp[i][j] + score(i, j, A[A.Length - 1]));
			}
		}
		
		Console.WriteLine(ans);
	}
	int N;
	int[] A;
	public Sol(){
		N = ri();
		A = ria();
		for(int i=0;i<A.Length;i++) A[i]--;
	}

	static String rs(){return Console.ReadLine();}
	static int ri(){return int.Parse(Console.ReadLine());}
	static long rl(){return long.Parse(Console.ReadLine());}
	static double rd(){return double.Parse(Console.ReadLine());}
	static String[] rsa(char sep=' '){return Console.ReadLine().Split(sep);}
	static int[] ria(char sep=' '){return Array.ConvertAll(Console.ReadLine().Split(sep),e=>int.Parse(e));}
	static long[] rla(char sep=' '){return Array.ConvertAll(Console.ReadLine().Split(sep),e=>long.Parse(e));}
	static double[] rda(char sep=' '){return Array.ConvertAll(Console.ReadLine().Split(sep),e=>double.Parse(e));}
}