#include <bits/stdc++.h>

#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = (int)a-1; ~i; --i)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b)
#define x first
#define y second
#define enter putchar('\n')

typedef long long ll;
typedef double DB;

const DB eps = 1e-12;
const DB PI = acos(-1);

int sgn(DB x) { return fabs(x) < eps ? 0 : (x > 0 ? 1 : -1); }

struct Point {
	DB x, y;
	Point(DB x = 0, DB y = 0) : x(x), y(y) {}
	Point operator + (Point a) { return Point(x + a.x, y + a.y); }
	Point operator - (Point a) { return Point(x - a.x, y - a.y); }
	Point operator - () { return Point(-x, -y); }
	friend Point operator * (DB k, Point a) { return Point(k * a.x, k * a.y); }
	DB operator % (Point a) { return x * a.x + y * a.y; }
	DB operator / (Point a) { return x * a.y - y * a.x; }
	operator DB() { return sqrt(x*x + y*y); }
	bool operator < (const Point &a) const { int d1 = sgn(x-a.x), d2 = sgn(y-a.y); return d2 < 0 || (d2 == 0 && d1 < 0); }
};

Point gp() { Point a; scanf("%lf%lf", &a.x, &a.y); return a; }
void wp(Point a) { printf("%.0lf %.0lf", a.x, a.y); }

struct Poly {
	std::vector<Point> a;
	Poly(int n = 0) { a.resize(n); rep0(i, n) a[i] = gp(); }
	int size() { return a.size(); }
	void add(Point P) { a.push_back(P); }
	Point &operator [] (int i) { int n = size(); return a[i >= n ? i-n : i]; }
};

int Direct1(Point P, Point P1, Point P2) { return sgn((P1-P)%(P2-P)); }
int Direct2(Point P, Point P1, Point P2) { return sgn((P1-P)/(P2-P)); }

DB Area(Poly A) {
	DB res = 0;
	rep(i, 2, A.size()-1) res += (A[i]-A[0])/(A[i-1]-A[0]);
	return fabs(res)/2;
}

bool IsConvex(Poly A) {
	bool f1 = 1, f2 = 1;
	rep0(i, A.size()) {
		int t = sgn((A[i+1]-A[i])/(A[i+2]-A[i+1]));
		t && (t == 1 ? f1 = 0 : f2 = 0);
	}
	return f1 || f2;
}

bool IsSegInter(Point P1, Point P2, Point Q1, Point Q2) {
	if (min(P1.x, P2.x) <= max(Q1.x, Q2.x) && max(P1.x, P2.x) >= min(Q1.x, Q2.x) && min(P1.y, P2.y) <= max(Q1.y, Q2.y) && max(P1.y, P2.y) >= min(Q1.y, Q2.y))
		if (Direct2(P1, P2, Q1) * Direct2(P1, P2, Q2) <= 0 && Direct2(Q1, Q2, P1) * Direct2(Q1, Q2, P2) <= 0) return 1;
	return 0;
}

int IsInPoly(Poly A, Point P) {
	DB angle = 2.0*PI*rand()/RAND_MAX; Point Q = P+1e5*Point(cos(angle), sin(angle));
	//wp(Q);enter;
	int cnt = 0;
	rep0(i, A.size()) {
		if (!Direct2(P, A[i], A[i+1]) && Direct1(A[i], A[i+1], P) * Direct1(A[i+1], A[i], P) >= 0) return -1;
		cnt ^= IsSegInter(A[i], A[i+1], P, Q);
	}
	return cnt;
}

Poly GetConvex(int n, Point P[]) {
	std::vector<int> s1, s2;
	rep(i, 1, n) {
		//wp(P[i]); enter;
		while (s1.size() > 1 && Direct2(P[s1[s1.size()-2]], P[s1[s1.size()-1]], P[i]) < 0) s1.pop_back();
		while (s2.size() > 1 && Direct2(P[s2[s2.size()-2]], P[s2[s2.size()-1]], P[i]) > 0) s2.pop_back();
		s1.push_back(i), s2.push_back(i);
	}
	Poly A;
	rep(i, 0, s1.size()-2) A.add(P[s1[i]]);
	per(i, s2.size()-1, 1) A.add(P[s2[i]]);
	return A;
}

int n;
Point a[100001];
Poly A;

int main() {
	scanf("%d", &n);
	rep(i, 1, n) a[i] = gp();
	std::sort(a+1, a+n+1);
	A = GetConvex(n, a);
	printf("%d\n", A.size());
	rep0(i, A.size()) {
		wp(A[i]); enter;
	}
	/*
	scanf("%d", &q);
	while (q--) {
		Point P1 = gp(), P2 = gp(), Q1 = gp(), Q2 = gp();
		printf("%.10lf\n", SegDist(P1, P2, Q1, Q2));
		//wp(GetSegInter(P1, P2, Q1, Q2)); enter;
		//printf("%d\n", IsInter(P1, P2, Q1, Q2));
		//wp(Proj(P, P1, P2)); enter;
	}*/
	return 0;
}


Measured memory_bytes: 7164