package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

var infty = 2000000000 + 10

// LeftChild returns index of the left child of the current node
func LeftChild(i int) int {
	return 2*i + 1
}

// RightChild returns index of the right child of the current node
func RightChild(i int) int {
	return 2 * (i + 1)
}

// Parent returns index of the parent node of the current node
func Parent(i int) int {
	return (i - 1) / 2
}

// NewMaxHeap creates MaxHeap object
func NewMaxHeap(content []int) *MaxHeap {
	maxheap := &MaxHeap{content: content}
	return maxheap
}

// MaxHeap is
// - content: zero-indexed array
type MaxHeap struct {
	heapSize int
	content  []int
}

func (h MaxHeap) maxHeapify(i int) {
	l := LeftChild(i)
	r := RightChild(i)

	// fmt.Println(i, l, r, a)

	var largest int
	if (l < h.heapSize) && (h.content[l] > h.content[i]) {
		largest = l
	} else {
		largest = i
	}
	if (r < h.heapSize) && (h.content[r] > h.content[largest]) {
		largest = r
	}

	if largest != i {
		h.swap(i, largest)
		h.maxHeapify(largest)
	}
}

func (h MaxHeap) swap(i, j int) {
	temp := h.content[i]
	h.content[i] = h.content[j]
	h.content[j] = temp
}

// BuildMaxHeap returns max-heapified heap
func BuildMaxHeap(a []int) MaxHeap {
	h := MaxHeap{heapSize: len(a), content: a}
	for i := h.heapSize/2 - 1; i >= 0; i-- {
		h.maxHeapify(i)
	}
	return h
}

// NewPriorityQueue creates PriorityQueue object
func NewPriorityQueue(content []int) *PriorityQueue {
	maxHeap := BuildMaxHeap(content)
	pq := &PriorityQueue{maxHeap}
	return pq
}

// PriorityQueue is MaxHeap based priority queue
type PriorityQueue struct {
	maxHeap MaxHeap
}

func (pq PriorityQueue) getMaximum() int {
	return pq.maxHeap.content[0]
}

func (pq *PriorityQueue) dropMaximum() int {
	if pq.maxHeap.heapSize < 1 {
		fmt.Println("heap under flow")
	}

	max := pq.getMaximum()
	pq.maxHeap.content[0] = pq.maxHeap.content[pq.maxHeap.heapSize-1]
	pq.maxHeap.heapSize--
	pq.maxHeap.maxHeapify(0)

	return max
}

func (pq *PriorityQueue) increaseKey(i, key int) {
	if key < pq.maxHeap.content[i] {
		_ = fmt.Errorf("key = %v < %v = a[i]", key, pq.maxHeap.content[i])
		return
	}
	pq.maxHeap.content[i] = key
	for (i >= 1) && (pq.maxHeap.content[Parent(i)] < pq.maxHeap.content[i]) {
		pq.maxHeap.swap(i, Parent(i))
		i = Parent(i)
	}
}

func (pq *PriorityQueue) insert(key int) {
	pq.maxHeap.heapSize++
	if len(pq.maxHeap.content) >= pq.maxHeap.heapSize {
		pq.maxHeap.content[pq.maxHeap.heapSize-1] = -infty
	} else {
		pq.maxHeap.content = append(pq.maxHeap.content, -infty)
	}
	pq.increaseKey(pq.maxHeap.heapSize-1, key)
}

func (pq *PriorityQueue) delete(i int) int {
	deletedElement := pq.maxHeap.content[i]
	pq.maxHeap.content[i] = -infty
	pq.maxHeap.swap(i, pq.maxHeap.heapSize-1)
	pq.maxHeap.heapSize--
	pq.maxHeap.maxHeapify(i)
	return deletedElement
}

var scanner = bufio.NewScanner(os.Stdin)

func nextString() string {
	scanner.Scan()
	return scanner.Text()
}

func nextInt() int {
	n, err := strconv.Atoi(nextString())
	if err != nil {
		fmt.Printf("strconv.Atoi failed: %v\n", err)
	}
	return n
}

func main() {
	scanner.Split(bufio.ScanWords)

	nQueues := nextInt()
	nQueries := nextInt()

	queues := make([]PriorityQueue, nQueues)
	for i := 0; i < nQueues; i++ {
		queues[i] = *NewPriorityQueue([]int{})
	}

	for i := 0; i < nQueries; i++ {
		queryType := nextInt()
		t := nextInt()
		if queryType == 0 { // insert
			x := nextInt()
			queues[t].insert(x)
		} else if queryType == 1 { // getMax
			if queues[t].maxHeap.heapSize > 0 {
				fmt.Println(queues[t].getMaximum())
			}
		} else if queryType == 2 { // deleteMax
			if queues[t].maxHeap.heapSize > 0 {
				queues[t].dropMaximum()
			}
		}
	}
}



Measured memory_bytes: 10716