use std::io::prelude::*;

fn input<T: std::str::FromStr>() -> T {
    let stdin = std::io::stdin();
    let token: String = stdin
        .lock()
        .bytes()
        .map(|c| c.unwrap() as char)
        .skip_while(|c| c.is_whitespace())
        .take_while(|c| !c.is_whitespace())
        .collect();
    token.parse().ok().unwrap()
}

fn with_stdout_lock<F: FnOnce(std::io::BufWriter<std::io::StdoutLock>)>(f: F) {
    let out = std::io::stdout();
    let w = std::io::BufWriter::new(out.lock());
    f(w);
}

fn main() {
    let n: usize = input();
    let mut d: Vec<(i64, usize)> = (0..n).map(|i| (input(), i)).collect();
    d.sort_by_key(|&(d, _)| -d);

    let mut edges = vec![vec![]; n];
    let mut uf = UnionFind::new(n);
    for i in 0..n - 1 {
        let (di, u) = d[i];
        let s = uf.size(u) as i64;
        let dj = di - n as i64 + 2 * s;
        let k = d.lower_bound_by_key(&Reverse(dj), |&(d, _)| Reverse(d));
        let (dk, v) = d[k];
        if dk != dj {
            println!("-1");
            return;
        }
        edges[u].push(v);
        edges[v].push(u);
        uf.union(u, v);
    }

    let mut dist = vec![-1; n];
    dfs(0, 0, &edges, &mut dist);
    let dist_sum: i64 = dist.iter().sum();
    d.sort_by_key(|&(_, v)| v);
    if dist_sum != d[0].0 {
        println!("-1");
        return;
    }

    with_stdout_lock(|mut w| {
        for u in 0..n {
            for &v in &edges[u] {
                if u < v {
                    writeln!(w, "{} {}", u + 1, v + 1).unwrap();
                }
            }
        }
    });
}

fn dfs(cur: usize, d: i64, edges: &Vec<Vec<usize>>, dist: &mut Vec<i64>) {
    dist[cur] = d;
    for &nxt in &edges[cur] {
        if dist[nxt] < 0 {
            dfs(nxt, d + 1, edges, dist);
        }
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
struct Reverse<T>(T);
impl<T: PartialOrd> PartialOrd for Reverse<T> {
    fn partial_cmp(&self, other: &Reverse<T>) -> Option<std::cmp::Ordering> {
        other.0.partial_cmp(&self.0)
    }
}
impl<T: Ord> Ord for Reverse<T> {
    fn cmp(&self, other: &Reverse<T>) -> std::cmp::Ordering {
        other.0.cmp(&self.0)
    }
}

trait LowerBound<T: Ord> {
    fn lower_bound_by<F: FnMut(&T) -> std::cmp::Ordering>(&self, f: F) -> usize;
    fn lower_bound(&self, x: &T) -> usize {
        self.lower_bound_by(|e| e.cmp(x))
    }
    fn lower_bound_by_key<U: Ord, F: FnMut(&T) -> U>(&self, x: &U, mut f: F) -> usize {
        self.lower_bound_by(|e| f(e).cmp(x))
    }
}
impl<T: Ord> LowerBound<T> for [T] {
    fn lower_bound_by<F: FnMut(&T) -> std::cmp::Ordering>(&self, mut f: F) -> usize {
        let mut l = -1 as i64;
        let mut r = self.len() as i64;
        while r - l > 1 {
            let m = ((l + r) / 2) as usize;
            if f(&self[m]) == std::cmp::Ordering::Less {
                l = m as i64;
            } else {
                r = m as i64;
            }
        }
        r as usize
    }
}

struct UnionFind {
    parent: Vec<usize>,
    size: Vec<usize>,
}
impl UnionFind {
    fn new(n: usize) -> UnionFind {
        UnionFind {
            parent: (0..n).collect(),
            size: vec![1; n],
        }
    }
    fn find(&mut self, x: usize) -> usize {
        if self.parent[x] == x {
            x
        } else {
            let p = self.parent[x];
            self.parent[x] = self.find(p);
            self.parent[x]
        }
    }
    fn union(&mut self, x: usize, y: usize) -> bool {
        let mut x = self.find(x);
        let mut y = self.find(y);
        if x == y {
            false
        } else {
            if self.size[x] < self.size[y] {
                std::mem::swap(&mut x, &mut y);
            }
            self.parent[y] = x;
            self.size[x] += self.size[y];
            true
        }
    }
    fn size(&mut self, x: usize) -> usize {
        let p = self.find(x);
        self.size[p]
    }
}


Measured memory_bytes: 22396