#include <bits/stdc++.h>

struct Edge {
	int to;

	int64_t cost;
	int64_t capacity;
	int rev_i;
};

using EdgeVec = std::vector<Edge>;
using EdgeLists = std::vector<EdgeVec>;

///////////////
// 最小費用流 //
///////////////

class MinCostFlow {
private:
	const int64_t inf_{LLONG_MAX};

	EdgeLists edges;
	const int source_, sink_;
	std::vector<int64_t> distance_;
	std::vector<bool> visited_;

	void calcDistance()
	{
		std::fill(distance_.begin(), distance_.end(), inf_);
		distance_[source_] = 0;
		std::queue<std::pair<int, int64_t>> que;
		que.push({source_, 0});
		while (!que.empty())
		{
			auto now{que.front()};
			que.pop();
			if (now.second > distance_[now.first]) continue;
			for (Edge& edge: edges[now.first])
				if (edge.capacity > 0 && distance_[now.first] + edge.cost < distance_[edge.to])
				{
					distance_[edge.to] = distance_[now.first] + edge.cost;
					que.push({edge.to, distance_[edge.to]});
				}
		}
	}

	int64_t calcCapacity(const int index, const int64_t flow)
	{
		if (index == source_) return flow;
		visited_[index] = true;
		for (Edge& prev_node: edges[index])
		{
			Edge& rev_edge{edges[prev_node.to][prev_node.rev_i]};
			if (visited_[prev_node.to] || rev_edge.capacity == 0 || distance_[prev_node.to] + rev_edge.cost != distance_[index]) continue;
			const int64_t additional_flow{calcCapacity(prev_node.to, std::min(flow, rev_edge.capacity))};
			if (additional_flow == 0) continue;
			prev_node.capacity += additional_flow;
			rev_edge.capacity -= additional_flow;
			return additional_flow;
		}
		return 0;
	}

public:
	MinCostFlow(const int node_num, const int source, const int sink)
		: edges(node_num), distance_(node_num), visited_(node_num), source_(source), sink_(sink) {}
	
	void addEdge(const int from, const int to, const int64_t cost, const int capacity)
	{
		edges[from].push_back({to, cost, capacity, (int)edges[to].size()});
		edges[to].push_back({from, -cost, 0, (int)edges[from].size() - 1});
	}

	int64_t operator()(const int64_t flow)
	{
		int64_t min_cost{};
		for (int64_t flow_rest{flow}; flow_rest > 0;)
		{
			calcDistance();
			if (distance_[sink_] == inf_) return inf_;
			std::fill(visited_.begin(), visited_.end(), false);
			int64_t additional_flow{calcCapacity(sink_, flow_rest)};
			flow_rest -= additional_flow;
			min_cost += additional_flow * distance_[sink_];
		}
		return min_cost;
	}
};


class Solve {
private:
	using i3 = std::array<int64_t, 3>;
	std::vector<i3> xyz;

	bool canContain(int in, int out)
	{
		for (int i{}; i < 3; i++)
			if (xyz[in][i] >= xyz[out][i])
				return false;
		return true;
	}

	int64_t calcVolume(int index)
	{
		int64_t ret{1};
		for (int i{}; i < 3; i++)
			ret *= xyz[index][i];
		return ret;
	}

public:
	bool is_last_query_{};
	Solve()
	{
		int N;
		scanf("%d", &N);
		if (N == 0)
		{
			is_last_query_ = true;
			return;
		}
		xyz.resize(N);
		for (auto& e: xyz)
		{
			for (auto& f: e) scanf("%lld", &f);
			std::sort(e.begin(), e.end());
		}
		MinCostFlow minCostFlow(2 * N + 2, 0, 2 * N + 1);
		for (int i{}; i < N; i++)
		{
			minCostFlow.addEdge(0, 1 + i, -calcVolume(i), 1);
			minCostFlow.addEdge(N + 1 + i, 2 * N + 1, 0, 1);
		}
		minCostFlow.addEdge(0, 2 * N + 1, 0, N);
		for (int i{}; i < N; i++)
			for (int j{}; j < N; j++)
				if (canContain(i, j))
					minCostFlow.addEdge(1 + i, N + 1 + j, 0, 1);

		int64_t ans{};
		for (int i{}; i < N; i++) ans += calcVolume(i);
		ans += minCostFlow(N);
		printf("%lld\n", ans);
	}
};


int main()
{
	while (!Solve().is_last_query_);

	return 0;
}


Measured memory_bytes: 3332