import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.reflect.ClassTag

object Main extends App {

  import scala.io.StdIn._

  implicit object G extends Group[Int] {
    override def plus(a: Int, b: Int): Int = a + b

    override def minus(a: Int, b: Int): Int = a - b

    override def times(a: Int, b: Int): Int = a * b

    override def maxSentinel: Int = Int.MaxValue

    override def zero: Int = 0
  }
  solve
  def solve: Unit = {
    val n = readLine.trim.toInt
    if (n == 0) return
    val dolls = Array.tabulate(n){_ ⇒
      val Array(x, y, z) = readLine.trim.split(' ').map(_.toInt)
      Doll(x, y, z)
    }
    val buffer = ArrayBuffer[(Int, Int, Int)]()
    for {i ← 0 until n;j ← 0 until n if dolls(i).canInclude(dolls(j))} {
      buffer.append((j, i, -dolls(j).weight))
    }
    if (buffer.nonEmpty) {
      val maxReduce = minCostMatching2(n, n, buffer.toArray, n)
      println(dolls.map {
        _.weight
      }.sum + maxReduce)
    }else {
      println(dolls.map{_.weight}.sum)
    }
    solve
  }
  case class Doll(x: Int, y: Int, z: Int){
    private val sorted = Array(x, y, z).sorted
    def weight: Int = x * y * z
    def canInclude(that: Doll): Boolean = (0 until 3).forall(i ⇒ sorted(i) > that.sorted(i))
  }
  implicit class Extend[T](value: T){
    def let[R](func: T ⇒ R): R = func(value)
    def also(func: T ⇒ Unit): T = {func(value); value}
  }
  trait Group[T]{
    def plus(a: T, b: T): T
    def minus(a: T, b: T): T
    def times(a: T, b: Int): T
    def maxSentinel: T
    def zero: T
  }

  private case class Edge[N](to: Int, pair: Int, capacity: Int, cost: N)
  def minCostMatching2[N: ClassTag](s: Int, t: Int, edges: Array[(Int, Int, N)], matchingLimit: Int)(implicit m: Group[N], o: Ordering[N]): N = {
    val nodes = makeGraph(s, t, edges)
    val source = 0
    val drain = s + t + 1
    val potential = Array.tabulate(s + t + 2){_ ⇒ m.maxSentinel}
    for ((_, to, cost) ← edges if o.compare(potential(to + s + 1), cost) > 0) potential(to + s + 1) = cost
    potential.indices.withFilter(i ⇒ o.compare(potential(i), m.maxSentinel) == 0).foreach(i ⇒ potential(i) = m.zero)
    val minCost = Array.tabulate(s + t + 2){_ ⇒ m.maxSentinel}
    val queue = mutable.PriorityQueue()(Ordering.by[(Int, N), N](_._2).reverse)
    val prevEdge = Array.tabulate(s + t + 2){_ ⇒ Edge(0, 0, 0, m.zero)}
    var canMatch = true
    var result = m.zero
    for (_ ← 1 to matchingLimit if canMatch){
      minCost.indices.foreach(i ⇒ minCost(i) = m.maxSentinel)
      minCost(source) = m.zero
      queue.clear()
      queue.enqueue((source, m.zero))
      while(queue.nonEmpty){
        val (prev, cost) = queue.dequeue()
        if (minCost(prev) == cost && prev != s + t + 1){
          for (Edge(to, pair, capacity, c) ← nodes(prev) if capacity == 1){
            //minCost(to) > cost + potential(prev) + c - potential(to)
            if (o.compare(minCost(to), m.plus(m.plus(cost, potential(prev)), m.minus(c, potential(to)))) > 0) {
              minCost(to) = m.plus(m.plus(cost, potential(prev)), m.minus(c, potential(to)))
              prevEdge(to) = nodes(to)(pair)
              queue.enqueue((to, minCost(to)))
            }
          }
        }
      }
      if (minCost(drain) == m.maxSentinel){
        canMatch = false
      }else {
        result = m.plus(result, minCost(drain))
        var last = drain
        while(last != source){
          val rev = prevEdge(last)
          val edge = nodes(rev.to)(rev.pair)
          nodes(last)(edge.pair) = rev.copy(capacity = 1)
          nodes(rev.to)(rev.pair) = edge.copy(capacity = 0)
          last = rev.to
        }
        for (x ← 1 until drain){
          potential(x) = m.plus(potential(x), minCost(x))
        }
      }
    }
    result
  }
  private def makeGraph[N](s: Int, t: Int, edges: Array[(Int, Int, N)])(implicit m: Group[N]): Array[Array[Edge[N]]]= {
    val e = Array.tabulate(s + t + 2){_ ⇒ ArrayBuffer[Edge[N]]()}
    for (x ← 1 to s){
      e(x).append(Edge(0, e(0).length, 0, m.zero))
      e(0).append(Edge(x, 0, 1, m.zero))
    }
    for (y ← s + 1 to s + t){
      e(y).append(Edge(s + t + 1, e(s + t + 1).length, 1, m.zero))
      e(s + t + 1).append(Edge(y, 0, 0, m.zero))
    }
    for ((from, to, cost) ← edges){
      e(from + 1).append(Edge(to + s + 1, e(to + s + 1).length, 1, cost))
      e(to + s + 1).append(Edge(from + 1, e(from + 1).length - 1, 0, m.minus(m.zero, cost)))
    }
    e.map(_.toArray)
  }
}


Measured memory_bytes: 208072