#include <cmath>
#include <string>
#include <iostream>
#include <algorithm>
#include<vector>
#include<cstring>
#include<set>
#include <cstdio>
#include<queue>
#include<map>


#define endl '\n'
#define forup(i,a,n) for( int i =a; i < n; i++)
#define llforup(i,a,n) for( ll i =a; i < n; i++)
#define fordown(i,a,n) for( int i =a; i >= n; i--)
#define fornot(i,a,n) for( int i=a;!n;i++)
#define mod  (ll)(1e9+7)
#define putnum(a) printf("%d\n",a)
using namespace std;
typedef long long ll;


inline int readnum()
{
	int p, data = 0; char ch = 0;
	while ((ch != '-') &&( ch < '0' || ch>'9') )//注意运算优先级，逻辑与大于逻辑非
		ch = getchar();//相当于正则表达式的搜索，跳过不需要的字符
	if (ch == '-')
		p = -1, ch = getchar();//不能与if平行放置，不懂为什么，可能是bug
	else p = 1;
	while (ch >= '0' && ch <= '9') data = data * 10 + ch - '0', ch = getchar();//搜索需要的字符
	return data * p;
}
inline int ReadPossitive()
{
	char ch; int data = 0;
	ch = getchar();
	while (ch == ' ' || ch == '\n')ch = getchar();
	while (ch != ' ' && ch != '\n' && ch != EOF)
	{
		data = data * 10 + ch - '0';
		ch = getchar();
	}
	return data;
}
//并查集模板
class DisjointSet {
private:
	vector<int>rank, parent;
public:
	DisjointSet(){}
	DisjointSet(int size)
	{
		rank.resize(size, 0);//默认初始化为0，养成习惯写完整
		parent.resize(size, 0);
		forup(i, 0, size)MakeSet(i);
	}
	void MakeSet(int x)
	{
		parent[x] = x;
		rank[x] = 0;
	}
	bool same(int x, int y)
	{
		return Findroot(x) == Findroot(y);
	}
	void unite(int x, int y)
	{
		link(Findroot(x), Findroot(y));
	}
	inline int Findroot(int x)
	{
		while (x != parent[x])
		{
			x = parent[x];
		}
		return x;
	}
	void link(int rootx, int rooty)
	{
		if (rank[rootx] < rank[rooty])parent[rootx] = parent[rooty];
		else
		{
			parent[rooty] = parent[rootx];
			if (rank[rootx] == rank[rooty])
				rank[rootx]++;
		}
	}
};

//计算几何模板
#define EPS (1e-10)//注意：<EPS代表数据<=0,>EPS代表数据>0,若要求数据<0请使用<-EPS进行判断
#define equals(a,b) (fabs((a)-(b))<EPS)//保留整数0
class Point {
public:
	double x, y;
public://p不能为引用的问题怎么解决？答用右值引用解决,两个以上的参数要都装载左右引用要四套板子，麻烦，所以引用的方法我们只对单参数使用
	Point() {};
	Point(double x,double y):x(x),y(y){}
	Point operator +(const Point &p) const{ return Point(x + p.x, y + p.y);}
	Point operator -(const Point &p) const{ return Point(x - p.x, y - p.y);}
	Point operator *(double a) const{ return Point(a * x, a * y); }
	Point operator /(double a) const{ return Point(x / a, y / a); }
	inline double abs() { return sqrt(norm()); }
	inline double norm() { return x * x + y * y; }
	bool operator <(const Point& p)const {
		return x != p.x ? x < p.x : y < p.y;
	}
	bool operator ==(const Point& p)const {
		return fabs(x - p.x) < EPS && fabs(y - p.y) < EPS;
	}
	//右值组
	Point operator +(Point&& p) const{ return Point(x + p.x, y + p.y); }
	Point operator -(Point&& p) const{ return Point(x - p.x, y - p.y); }
};
typedef Point Vector;

class Line {
public:
	Point p1, p2;
public:
	Line() {};
	Line(int p1x, int p1y, int p2x, int p2y)
	{
		p1.x = p1x; p1.y = p1y;
		p2.x = p2x; p2.y = p2y;
	}
};

class Circle {
public:
	Point c; double r;
public:
	Circle() {}
	Circle(Point c,double r):c(c),r(r){}
	Circle(double cx, double cy, double cr)
	{
		c.x = cx; c.y = cy; r = cr;
	}
};

typedef vector<Point> _Polygon;

typedef vector<Point> PointSet;

class VectorOperation {//操作符不必继承
public:
	double dot(Point a, Point b)const//点乘
	{
		return a.x * b.x + a.y * b.y;
	}
	double cross(Point a, Point b)const//叉乘
	{
		return a.x * b.y - a.y * b.x;
	}
	double abs(Vector s)
	{
		return sqrt(s.norm());
	}
	double getDistance(Point a, Point b)
	{
		return abs(a - b);
	}
	int PositionPL(Point p0, Point p1, Point p2)
	{
#define COUNTER_CLOCKWISE -1
#define CLOCKWISE 1
#define ONLINE_BACK -2
#define ONLINE_FRONT 2
#define ON_SEGMENT 0
		Vector a = p1 - p0;Vector b = p2 - p0;
		double Cross = cross(a, b);
		if (Cross > 0.0)return COUNTER_CLOCKWISE;
		if (Cross < 0.0)return CLOCKWISE;
		if (dot(a, b) < 0.0)return ONLINE_BACK;
		if (a.norm() < b.norm())return ONLINE_FRONT;

		return ON_SEGMENT;
#undef COUNTER_CLOCKWISE 
#undef CLOCKWISE 
#undef ONLINE_BACK 
#undef ONLINE_FRONT 
#undef ON_SEGMENT 
	}
	double inclination(Vector s)
	{
		return atan2(s.y, s.x);//取值范围-pi~pi
	}
	Vector Polar(double a, double r) { return Vector(cos(a) * r, sin(a) * r); }
	Vector rotate(Point a, double rad)//逆时针旋转
	{
		return Vector(a.x * cos(rad) - a.y * sin(rad), a.x * sin(rad) + a.y * cos(rad));
	}
};

class LineOperation :public VectorOperation{
public:	
	bool isOrthogonal(Line s1, Line s2)
	{
		return equals(dot(s1.p2-s1.p1, s2.p2 - s2.p1), 0.0);//操作符重载的返回值的内存分配机制尚不可知，似乎返回值没有分配内存，dot的参数不能用引用，否则报错。
	}
	bool isParallel(Line s1, Line s2)
	{
		return equals(cross(s1.p2 - s1.p1, s2.p2 - s2.p1), 0.0);//外积判断平行，内积判断垂直。
	}
	bool intersectSS(Line s1, Line s2)
	{
		return (PositionPL(s1.p1, s1.p2, s2.p1) * PositionPL(s1.p1, s1.p2, s2.p2) <= 0 &&
			    PositionPL(s2.p1, s2.p2, s1.p1) * PositionPL(s2.p1, s2.p2, s1.p2) <= 0);//即对两条线段都有另一条线段的两个端点必须在当前线段的两侧
	}//可以证明如果一条线段的计算结果是负二，另一个线段的计算结果必定大于0
	Point projection(Line s, Point p)
	{
		Vector base = s.p1 - s.p2;
		double r = dot(p - s.p1, base) /base.norm();
		return s.p1 + base * r;
	}
	Point reflection(Line s, Point p)
	{
		return p + (projection(s, p)-p) * 2.0;
	}
	double getDistanceLP(Line s, Point p)//点与直线
	{
		return fabs(cross(s.p2 - s.p1, p - s.p1)) / abs(s.p2 - s.p1);
	}
	double getDistanceSP(Line s, Point p)//点与线段
	{
		if (dot(s.p2 - s.p1, p - s.p1) < 0.0)return abs(p - s.p1);//与点p1成钝角
		else if (dot(s.p2 - s.p1, p - s.p2) > 0.0)return abs(p - s.p2);
		else return getDistanceLP(s, p);
	}
	double getDistanceSS(Line s1, Line s2)//线段与线段
	{
		//考虑
		// --
		//----
		if (intersectSS(s1, s2))return 0.0;//如果枚举优化需要16次逻辑，采用分治法的效率和直接使用min函数一样
		else
			return min(min(getDistanceSP(s1, s2.p1), getDistanceSP(s1, s2.p2)), min(getDistanceSP(s2, s1.p1), getDistanceSP(s2, s1.p2)));
	}
	Point getCrossPointSS(Line s1, Line s2)
	{
		Vector base = s2.p2 - s2.p1;
		double d1 = fabs(cross(base, s1.p1 - s2.p1));//除以abs(base)这步因为在t中base被消去所以没有写，提升效率
		double d2 = fabs(cross(base, s1.p2 - s2.p1));
		double t = d1 / (d1 + d2);
		return s1.p1 + (s1.p2 - s1.p1) * t;
	}
	Point getCrossPointLL(Line s1, Line s2)
	{
		Vector a = s1.p2 - s1.p1;
		Vector b = s2.p2 - s2.p1;
		return (s1.p1 + a*(cross(b, s2.p1 - s1.p1)) / cross(b, a));
	}
};

class CircleOperation :public VectorOperation{
public:
	int intersectCL(Line s,Circle c)
	{
		LineOperation op;
		double distance = op.getDistanceLP(s, c.c);
		double comparation = distance - c.r;
		if (comparation < EPS)return 1;
		if (comparation < 0.0)return 2;
		if (comparation > 0.0)return 0;
	}
	int intersectCC(Circle c1, Circle c2)
	{
		double distanceR = c1.r + c2.r;
		double distancePR = getDistance(c1.c, c2.c);
		double comparation = distancePR - distanceR;
		if (comparation < EPS)return 1;
		if (comparation > 0.0)return 0;
		if (comparation < 0.0)return 2;
	}
	pair<Point,Point> getCrossPointCL(Circle c,Line s)
	{
		LineOperation op;
		Point CLprojection = op.projection(s, c.c);
		Vector CL = CLprojection - c.c;
		Vector e = (s.p2 - s.p1) / abs(s.p2 - s.p1);
		double base = sqrt(c.r * c.r - CL.norm());
		return make_pair(CLprojection + e * base, CLprojection - e * base);
		//可以处理仅有一个交点的情况base是0产生两个同解。sqrt要求必须是正数，使用前必须进行有解性判断。
	}
	pair<Point, Point> getCrossPointCC(Circle c1, Circle c2)
	{
		double CC = abs(c1.c - c2.c);
		double a = acos((c1.r * c1.r + CC * CC - c2.r * c2.r) / (2 * c1.r * CC));//余弦定理
		double t = inclination(c2.c - c1.c);
		return make_pair(c1.c + Polar(t + a, c1.r), c1.c + Polar(t - a, c1.r));
	}
};


class PolygonOperation :public VectorOperation{
public:
	int PointsContainment(_Polygon &g,Point p)//ON_SEGMENT 0 IN 1 OUT 2
	{
#define ON_SEGMENT 0
#define IN 1
#define OUT 2
		int size = g.size();
		bool x = false;
		Vector a, b;
		forup(i, 0, size)
		{
			a = g[i] - p; b = g[(i+1)%size] - p;//用取模创造循环
			if (fabs(cross(a, b)) < EPS && dot(a, b) < EPS)return ON_SEGMENT;//<EPS包含了内积为负数的情况
			if (a.y < b.y)
			{
				double test = cross(a, b);
				if (a.y < EPS && EPS<b.y && cross(a, b)>EPS)x = !x;
			}
			else
			{
				if (b.y < EPS && EPS<a.y && cross(b, a)>EPS)x = !x;//射线平行x轴指向x轴正向,如果是.----平行的情况跳过，不会更改x值EPS<a.y不成立
			}//如果是-----.的情况，则会“触碰“到两条边，该判断方法仍然正确：触碰到偶数条边时点在多边形外部
		}
		return x ? IN : OUT;
#undef ON_SEGMENT
#undef IN
#undef OUT
	}
	_Polygon andrewScan(_Polygon s)
	{
#define COUNTER_CLOCKWISE -1
#define CLOCKWISE 1
#define ONLINE_BACK -2
#define ONLINE_FRONT 2
#define ON_SEGMENT 0
		_Polygon u, l;
		if (s.size() < 3)return s;
		sort(s.begin(), s.end());
		u.push_back(s[0]);
		u.push_back(s[1]);
		l.push_back(s[s.size() - 1]);
		l.push_back(s[s.size() - 2]);
		for (int i = 2; i < s.size(); i++)
		{
			for (int n = u.size(); n >= 2 ; n--) {
				int test = PositionPL(u[n - 2], u[n - 1], s[i]);
				if (test == COUNTER_CLOCKWISE)
				u.pop_back();
				else break;
			}
				
			u.push_back(s[i]);
			
		}
		for (int i = s.size() - 3; i >= 0; i--)
		{
			
			for (int n = l.size(); n >= 2 ; n--)
			{
				int test = PositionPL(l[n - 2], l[n - 1], s[i]);
				if(test==COUNTER_CLOCKWISE)//如果即将新加入的点与倒数第二、倒数第一个点组成的向量成逆时针方向（倒数第二指向倒数第一），就删除倒数第一个点
				l.pop_back();
				else break;
			}
			l.push_back(s[i]);
		}
#undef COUNTER_CLOCKWISE 
#undef CLOCKWISE 
#undef ONLINE_BACK 
#undef ONLINE_FRONT 
#undef ON_SEGMENT 
		reverse(l.begin(), l.end());//按最小横坐标为起点逆时针方向输出
		for (int i = u.size() - 2; i >= 1; i--)l.push_back(u[i]);//除掉首尾
		return l;
	}
};
//E-mail 374656045@qq.com

class solve {
private:
	int n, q;
public://采用邻接表建图,带权无向图
	solve()
	{
		n = readnum();
		int x, y;
		PointSet ps;
		PolygonOperation op;
		_Polygon ans;
		forup(i, 0, n)
		{
			x = readnum(); y = readnum();
			Point p(x, y);
			ps.push_back(p);
		}
		ans = op.andrewScan(ps);//题目要求
		int size = ans.size();
		printf("%d\n", size);
		int cnt = 0;
		while (ans[cnt].y > ans[cnt + 1].y)cnt++;
		forup(i, 0, size)
			printf("%d %d\n", (int)ans[(cnt+i)%size].x, (int)ans[(cnt+i)%size].y);//浮点型坐标根据题目需要必须转化为强制转化为int型才能使用printf正确输出
	}

};
int main(void)
{
	ios_base::sync_with_stdio(0); cin.tie(0);//慎用，cin和scanf混用会导致非MSC++WA
#ifdef DEBUG
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	solve solution;
#ifdef DEBUG
	fclose(stdin);//关闭文件 
	fclose(stdout);//关闭文件 
#endif
	return 0;

}




Measured memory_bytes: 9284