#include<utility>
#include<vector>
#include<functional>
#include<algorithm>
#include<iostream>
#include<set>

using namespace std;

struct Edge {
	int from, to; //if you want cost, etc., you should care about the "emplace_back"(line36,line118).
	Edge(int f, int t) {
		from = f;
		to = t;
	}
};

using Edges = vector<Edge>;
using Graph = vector<Edges>;

pair<vector<int>, Edges> bridge(const Graph& g) {
	const int n = g.size();
	int idx = 0, s = 0, t = 0, k = 0;
	vector<int>ord(n, -1), onS(n), stk(n), roots(n), cmp(n);
	Edges brdg;
	function<void(int, int)> dfs = [&](int v, int u) {
		ord[v] = idx++;
		stk[s++] = v;
		onS[v] = true;
		roots[t++] = v;
		for (auto& e : g[v]) {
			int w = e.to;
			if (ord[w] == -1)dfs(w, v);
			else if (u != w && onS[w])
				while (ord[roots[t - 1]]>ord[w])--t;
		}
		if (v == roots[t - 1]) {
			brdg.emplace_back(u, v);
			while (true) {
				int w = stk[--s];
				onS[w] = false;
				cmp[w] = k;
				if (v == w)break;
			}
			--t;
			++k;
		}
	};
	for (int u = 0; u<n; ++u) {
		if (ord[u] == -1) {
			dfs(u, n);
			brdg.pop_back();
		}
	}
	return make_pair(cmp, brdg);
}

vector<int>ArticulationPoint(const Graph& g) {
	int n = g.size(), idx = 0;
	vector<int>low(n), ord(n), art;
	function<void(int)>dfs = [&](int v) {
		low[v] = ord[v] = ++idx;
		for (auto& e : g[v]) {
			int w = e.to;
			if (ord[w] == 0) {
				dfs(w);
				low[w] = min(low[v], low[w]);
				if ((ord[v] == 1 && ord[w] != 2) || (ord[v] != 1 && low[w] >= ord[v]))
					art.push_back(v);
			}
			else
				low[v] = min(low[v], ord[w]);
		}
	};
	for (int u = 0; u<n; ++u) {
		if (ord[u] == 0) {
			idx = 0;
			dfs(u);
		}
	}
	return art;
}

vector<int> tarjan(const Graph& g) {
	int n = g.size(), idx = 0, k = 0, s = 0;
	vector<int>ord(n, -1), low(n), onS(n), cmp(n), stk(n);
	function<void(int)>dfs;
	dfs = [&](int v) {
		ord[v] = low[v] = idx++;
		stk[s++] = v;
		onS[v] = true;
		for (auto &e : g[v]) {
			int w = e.to;
			if (ord[w] == -1) {
				dfs(w);
				low[v] = min(low[v], low[w]);
			}
			else if (onS[w]) {
				low[v] = min(low[v], ord[w]);
			}
		}
		if (low[v] == ord[v]) {
			while (1) {
				int w = stk[--s];
				onS[w] = false;
				cmp[w] = k;
				if (w == v)break;
			}
			++k;
		}
	};
	for (int v = 0; v<n; ++v)
		if (ord[v] == -1)dfs(v);
	return cmp;
}

vector<int> kosaraju(const Graph& g) {
	int n = g.size(), sz = 0;
	Graph rg(n);
	vector<int>stk, cmp(n, -1), added(n), visited(n), ord(n);
	for (auto &es : g) {
		for (auto &e : es)rg[e.to].emplace_back(e.to, e.from);
		sz += es.size();
	}
	stk.resize(n + sz);
	sz = 0;
	for (int i = 0; i<n; ++i) {
		if (visited[i])continue;
		int s = 0;
		stk[s++] = i;
		while (s != 0) {
			int v = stk[s - 1];
			visited[v] = true;
			bool pushed = false;
			for (auto &e : g[v]) {
				int to = e.to;
				if (!visited[to]) {
					stk[s++] = to;
					pushed = true;
				}
			}
			if (pushed)continue;
			int t = stk[s - 1];
			if (!added[t]) {
				added[t] = true;
				ord[n - ++sz] = t;
			}
			--s;
		}
	}
	int k = 0;
	for (int &u : ord) {
		if (cmp[u] != -1)continue;
		int s = 0;
		stk[s++] = u;
		while (s != 0) {
			int v = stk[--s];
			cmp[v] = k;
			for (auto &e : rg[v]) {
				int t = e.to;
				if (cmp[t] == -1)stk[s++] = t;
			}
		}
		++k;
	}
	return cmp;
}

int main() {
	int V, E;
	cin >> V >> E;
	Graph g(V);
	for (int i = 0; i<E; ++i) {
		int s, t; cin >> s >> t;
		g[s].emplace_back(s, t);
		g[t].emplace_back(t, s);
	}
	Edges es = bridge(g).second;
	vector<pair<int, int>>v;
	for (auto x : es) {
		if(x.from>x.to)v.push_back(pair<int, int>(x.to, x.from));
		else v.push_back(pair<int, int>(x.from, x.to));
	}
	sort(v.begin(), v.end());
	for (auto x : v) {
		cout << x.first << " "  << x.second << endl;
	}
}

Measured memory_bytes: 5064