#include <bits/stdc++.h>
#define REP(i,n) for (long long i=0;i<(n);i++)
#define FOR(i,a,b) for (long long i=(a);i<(b);i++)
using namespace std;
constexpr long loINF = 2147483647;
constexpr double eps = (1e-10);
bool eq(double a,double b){ return a>b?a-b<eps:b-a<eps; }
class Point { //位置ベクトルと作用ベクトルを同一視
public:
	double x;
	double y;
	Point(double X=0.0,double Y=0.0):x(X),y(Y){}
	Point(const Point &m):x(m.x),y(m.y) {}
	Point operator+(const Point &p){return Point(x+p.x,y+p.y);}
	Point operator-(const Point &p){return Point(x-p.x,y-p.y);}
	Point operator*(const double k){return Point(x*k,y*k);}
	Point operator/(const double k){return Point(x/k,y/k);}
	Point operator=(const Point &p){x=p.x;y=p.y;return *this;}
	Point rev() {return Point(-x,-y);}
	bool operator<(const Point &p){return x!=p.x?x<p.x:y<p.y;}
	bool operator>(const Point &p){return x!=p.x?x>p.x:y>p.y;}
	bool operator==(const Point &p){return eq(x,p.x)&&eq(y,p.y);}
	double norm(){ return x*x+y*y; }
	double abs() { return sqrt(norm());}
	double dist(const Point &a) { return (Point(x,y)-a).abs(); }
	double dot(const Point &a) { return x*a.x + y*a.y; }
	//外積 -> 2vec向き付き角度判定,平行四辺形面積
	double det(const Point &a) { return x*a.y - y*a.x; }
	bool is_parallel(const Point &a) { return eq((x*a.y - y*a.x),0); }
	// a -> this の正射影
	Point proj(const Point &a) { return Point(x,y)*(dot(a)/norm()); }
	//2ベクトルのなす角 (正 : 時計回り, 負 : 反時計)
	double ang(const Point &v) {
		double g1 = atan2(v.y,v.x);
		double g2 = atan2(y,x);
		if (g1 < 0) g1 += (2 * M_PI);
		if (g2 < 0) g2 += (2 * M_PI);
		return (g2 - g1);
	}
	//2ベクトルのなす角(正値)を出す。
	double absAng(Point &v){ 
    	return acos(dot(v)/sqrt(norm() * v.norm()));
  	}

};

class Segment {
public:
	Point p1;
	Point p2;
	Segment(double x1,double y1,double x2,double y2) : p1(x1,y1),p2(x2,y2) {}
	Segment(const Point &a,const Point &b) : p1(a),p2(b) {}
	double len (const Segment &l) { return (p2 - p1).norm(); }
	inline Point toVec() { return (p2 - p1); }
};

// 線分Xと線分Yの交点
Point intersection(Segment X,Segment Y) {
	return X.p1 + (X.p2 - X.p1) * (Y.p2 - Y.p1).det((Y.p1 - X.p1)) / (Y.p2 - Y.p1).det(X.p2 - X.p1);
}

// 線分X上に点qがあるか
bool on_seq(Segment X,Point q) {
	return (eq((X.p1 - q).det(X.p2 - q),0) && eq((X.p1 - q).dot(X.p2 - q),0));
}

// 線分Xと線分Yが交点を持つか
bool have_intersection(Segment X,Segment Y) {
	if (!(X.toVec()).is_parallel(Y.toVec())) {
		Point r = intersection(X,Y);
		return (on_seq(X,r) && on_seq(Y,r));
    }
    else return (on_seq(X,Y.p1) || on_seq(X,Y.p2) || on_seq(Y,X.p1) || on_seq(Y,X.p2));
}

// 二つの線分の位置関係
int ccw(Point a,Point b,Point c) { // aが支点
	complex<double> v1(b.x-a.x,b.y-a.y),v2(c.x-a.x,c.y-a.y);
	complex<double> v = (v2*conj(v1));
	if(v.imag()>eps)return 1;        //反時計回り
	if(v.imag()<-eps)return -1;      //時計回り
	if(v.real()<-eps)return 2;       //真逆
	if(abs(v1)+eps<abs(v2))return -2;//同じ側超えてる
	return 0;                        //同じ側近い側
}
// 線分Xと点qの距離
double distance1 (Segment X,Point q) { 
	if (eq((X.p1 - X.p2).dot(q - X.p2),0)) return X.p2.dist(q);
	if (eq((X.p2 - X.p1).dot(q - X.p1),0)) return X.p1.dist(q);
	else return (X.p1 + (X.p2-X.p1).proj(q-X.p1)).dist(q);
}

//線分Xと線分Yの距離
double distance2 (Segment X,Segment Y) { 
	if (have_intersection(X,Y)) return 0.0;
	else return min({distance1(X,Y.p1),distance1(X,Y.p2),distance1(Y,X.p1),distance1(Y,X.p2)});
}

//点列をx座標で昇順ソート
inline void sort_x(vector<Point> &p) { 
	struct comp { bool operator() (const Point &a,const Point &b) {return (a.x!=b.x?a.x<b.x:a.y<b.y);} };
	sort(p.begin(),p.end(),comp());
}

//点列をy座標で昇順ソート
inline void sort_y(vector<Point> &p) { 
	struct comp { bool operator() (const Point &a,const Point &b) {return (a.y!=b.y?a.y<b.y:a.x<b.x);} };
	sort(p.begin(),p.end(),comp());
}

//多角形の面積 (point列は順番になっていること)
double getArea(vector<Point> &point){
	double s = 0;
	int n = point.size();
	for (int i = 0;i < n;i++ ) s += point[i%n].det(point[(i+1)%n]);
	return abs(s) * 0.5;
}

//0: outside, 1: on line, 2: inside (Winding Number)
int contains(const vector<Point> &point, Point p) {
    int flg = -1;
    Point _a, _b = point.back();
    for (int i = 0; i < int(point.size()); i++) {
        _a = _b; _b = point[i];
        Point a = _a, b = _b;
        if (ccw(a, b, p) == 0) return 1;
        if (a.y > b.y) swap(a, b);
        if (!(a.y <= p.y && p.y < b.y)) continue;
        // (a-p).det(b-p) > eps) : 線の内側
        if (eq(a.y,p.y) ? (a.x > p.x):((a-p).det(b-p) > eps)) flg *= -1;
    }
    return flg + 1;
}

//凸包を求める
vector<Point> convexHull(vector<Point> &point){
	int n = point.size(),k = 0;
	vector<Point> qs(n*2);
	sort_x(point);
	for(int i = 0;i < n;i++){ //下側凸包の作成
		while(k > 1 && (qs[k-1] - qs[k-2]).det(point[i] - qs[k-1]) < (-eps)) k--;
		// 平行な点を削除 -> while(k > 1 && (qs[k-1] - qs[k-2]).det(point[i] - qs[k-1]) < (eps)) k--;
		qs[k++] = point[i];
	}
	for(int i = n - 2, t = k; i >= 0; i--){ //上側凸包の作成
		while(k > t && (qs[k-1] - qs[k-2]).det(point[i] - qs[k-1]) <= (-eps)) k--;
		qs[k++] = point[i];
	}
	qs.resize(k-1);
	return qs;
}

int main(void){
	long n,q,x,y;
	cin >> n;
	vector<Point> pp(n);
	REP(i,n) {
		cin >> x >> y;
		pp[i] = Point(x,y);
	}
	vector<Point> ans = convexHull(pp);
	n = ans.size();
	cout << n << endl;
	long miny = loINF,idx;
	REP(i,n) { 
		if (ans[i].y < miny) {
			miny = ans[i].y;
			idx = i;
		}
	}
	FOR(i,idx,n+idx) {
		cout << ans[i%n].x << " " << ans[i%n].y << endl;
	}
}


Measured memory_bytes: 7488