// Forgotten tree (H), by Errichto
#include<bits/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i = (a); i <= (b); ++i)
#define RI(i,n) FOR(i,1,(n))
#define REP(i,n) FOR(i,0,(n)-1)

char sl[10];
int e[10][10], e_memo[10][10];
vector<int> group[10];
int boss[10];

typedef vector<pair<int,int>> Tree;

vector<int> w[10];
bool vis[10];
void dfs(int a) {
	vis[a] = true;
	for(int b : w[a]) if(!vis[b]) dfs(b);
}
bool validTree(Tree t, int k) {
	for(pair<int,int> edge : t) {
		w[edge.first].push_back(edge.second);
		w[edge.second].push_back(edge.first);
	}
	dfs(1);
	bool ans = true;
	RI(i, k) if(!vis[i]) ans = false;
	RI(i, k) vis[i] = false;
	RI(i, k) w[i].clear();
	return ans;
}

bool checkEverything(int k) { // O(k * 2^k)
	REP(mask, (1 << k)) {
		vector<int> subset;
		REP(i, k) if(mask & (1 << i)) subset.push_back(i + 1);
		int vertices = 0, edges = 0;
		for(int i : subset) vertices += group[i].size() - 1;
		for(int i : subset) for(int j : subset) edges += e[i][j];
		if(edges > vertices) return false;
	}
	return true;
}

void write(Tree t) {
	for(pair<int,int> edge : t) printf("%d-%d\n", edge.first, edge.second);
	puts("");
}

void tryTree(Tree t, int k) {
	RI(i, k) RI(j, k) e[i][j] = e_memo[i][j];
	for(pair<int,int> p : t) {
		int & tmp = e[p.first][p.second];
		if(tmp == 0) return;
		--tmp;
	}
	if(!checkEverything(k)) return;
	vector<pair<int,int>> ans;
	for(pair<int,int> p : t) ans.push_back({boss[p.first], boss[p.second]}); //
	RI(i, k) RI(j, k) while(e[i][j]) {
		--e[i][j];
		if((int) group[i].size() > 1) {
			int memo = group[i].back();
			group[i].pop_back();
			if(checkEverything(k)) {
				ans.push_back({boss[j], memo});
				// printf("%d %d\n", boss[j], group[i].back());
				continue;
			}
			group[i].push_back(memo);
		}
		assert((int) group[j].size() > 1);
		ans.push_back({boss[i], group[j].back()});
		// printf("%d %d\n", boss[i], group[j].back());
		group[j].pop_back();
		assert(checkEverything(k));
	}
	RI(i, k) assert((int) group[i].size() == 1);
	random_shuffle(ans.begin(), ans.end());
	for(pair<int,int> p : ans) {
		if(rand()%2) swap(p.first, p.second);
		printf("%d %d\n", p.first, p.second);
	}
	exit(0);
}

vector<Tree> findTrees(int k) {
	vector<Tree> ans;
	vector<pair<int,int>> edges;
	RI(i, k) FOR(j, i+1, k) edges.push_back({i, j});
	REP(mask, (1 << edges.size())) if(__builtin_popcount(mask) == k - 1) {
		Tree t;
		REP(i, (int) edges.size()) if(mask & (1 << i)) t.push_back(edges[i]);
		if(validTree(t, k)) ans.push_back(t);
	}
	return ans;
}

int findLog(int n) {
	int k = 0;
	while(n) {
		++k;
		n /= 10;
	}
	return k;
}

int main() {
	srand(42);
	int n;
	scanf("%d", &n);
	RI(i, n) group[findLog(i)].push_back(i);
	REP(_, n - 1) {
		scanf("%s", sl);
		int a = strlen(sl);
		scanf("%s", sl);
		int b = strlen(sl);
		if(a > b) swap(a, b);
		++e[a][b];
	}
	int k = findLog(n);
	RI(i, k) boss[i] = group[i][0];
	RI(i, k) RI(j, k) e_memo[i][j] = e[i][j];
	vector<Tree> trees = findTrees(k);
	for(Tree t : trees) tryTree(t, k);
	puts("-1");
	return 0;
}