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

#define DUMP(x) cerr << #x << "=" << x << endl
#define DUMP2(x, y) cerr<<"("<<#x<<", "<<#y<<") = ("<<x<<", "<<y<<")"<< endl
#define BINARY(x) static_cast<bitset<16> >(x)

#define rep(i,n) for(int i=0;i<(int)(n);i++)
#define REP(i,m,n) for (int i=m;i<(int)(n);i++)

#define in_range(x, y, w, h) (0<=(int)(x) && (int)(x)<(int)(w) && 0<=(int)(y) && (int)(y)<(int)(h))
#define ALL(a) (a).begin(),(a).end()

typedef long long ll;
const int INF   = 1e9;
const ll  INFLL = 1e18;
typedef pair<int, int> PII;
int dx[4]={0, -1, 1, 0}, dy[4]={-1, 0, 0, 1};

struct Edge { 
    int to, cap, rev, cost; 
    // Edge (int _to, int _cap, int _rev, int _cost) : to(_to), cap(_cap), rev(_rev), cost(_cost) {}
};

class MinCostFlow {
public:
    int N;
    vector<vector<Edge>> G;
    vector<int> dist, prevv, preve;

    MinCostFlow(int _n) : N(_n), G(_n, vector<Edge>()), dist(vector<int>(_n)),
        prevv(vector<int>(_n)), preve(vector<int>(_n)) {}

    void add_edge(int from, int to, int cap, int cost)
    {
        G[from].push_back({to, cap, (int)G[to].size(), cost});
        G[to].push_back({from, 0, (int)G[from].size()-1, -cost});
    }

    int min_cost_flow(int s, int t, int f)
    {
        int res = 0;
        while (f > 0) {
            fill(dist.begin(), dist.end(), INF);
            dist[s] = 0;
            bool update = true;
            while (update) {
                update = false;
                for (int v=0; v<N; v++) {
                    if (dist[v] == INF) continue;
                    for (int i=0; i<(int)G[v].size(); i++) {
                        Edge &e = G[v][i];
                        if (e.cap > 0 && dist[e.to] > dist[v] + e.cost) {
                            dist[e.to] = dist[v] + e.cost;
                            prevv[e.to] = v;
                            preve[e.to] = i;
                            update = true;
                        }
                    }
                }
            }
            if (dist[t] == INF) return -1;

            int d = f;
            for (int v=t; v!=s; v=prevv[v]) {
                d = min(d, G[prevv[v]][preve[v]].cap);
            }
            f -= d;
            res += d * dist[t];
            for (int v=t; v!=s; v=prevv[v]) {
                Edge &e = G[prevv[v]][preve[v]];
                e.cap -= d;
                G[v][e.rev].cap += d;
            }
        }
        return res;
    }
};

struct Block {
    int x[3];
    void sort() { std::sort(x, x+3); }
    bool can_contain(Block &rhs)
    {
        sort();
        rhs.sort();
        return (x[0] > rhs.x[0] && x[1] > rhs.x[1] && x[2] > rhs.x[2]);
    }
};

int main()
{
    ios::sync_with_stdio(false);

    int N;
    while (cin >> N, N) {
        vector<Block> B(N);
        rep(i, N) {
            int X, Y, Z;
            cin >> X >> Y >> Z;
            B[i] = {X, Y, Z};
        }

        const int SOURCE = 2*N;
        const int SINK   = 2*N+1;
        MinCostFlow flow(2*N+2);

        rep(i, N) {
            int cost = B[i].x[0] * B[i].x[1] * B[i].x[2];
            flow.add_edge(SOURCE, i, 1, 0);
            flow.add_edge(i, SINK, 1, cost);
            flow.add_edge(N + i, SINK, 1, 0);
        }

        rep(i, N) {
            rep(j, N) {
                if (i == j) continue;
                if (B[j].can_contain(B[i])) {
                    flow.add_edge(i, N+j, 1, 0);
                }
            }
        }

        /*
        int ans = INF;
        rep(i, N) {
            int c = flow.min_cost_flow(SOURCE, SINK, i+1);
            if (c >= 0) ans = min(ans, c);
        }
        cout << ans << endl;
        */

        cout << flow.min_cost_flow(SOURCE, SINK, N) << endl;
    }
}

Measured memory_bytes: 3284