#include <bits/stdc++.h>

#define FOR(i,a,b) for (int i=(a);i<(b);i++)
#define RFOR(i,a,b) for (int i=(b)-1;i>=(a);i--)
#define REP(i,n) for (int i=0;i<(n);i++)
#define RREP(i,n) for (int i=(n)-1;i>=0;i--)
#define pb push_back
#define all(a) (a).begin(),(a).end()

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template <class T, class U> void amax(T& x, U y) {if (x < y) x = y;}
template <class T, class U> void amin(T& x, U y) {if (x > y) x = y;}

class point
{
    public:
        int x;
        int y;
        point(int x, int y) : x(x), y(y) {}
        point operator +(point p) {return point(x + p.x, y + p.y);}
        point operator -(point p) {return point(x - p.x, y - p.y);}
        string show() {return "(" + to_string(x) + ", " + to_string(y) + ")";}
};

// returns  0 if p0, p1, p2 are colinear,
//          1 if p0, p1, p2 are counter-clockwise,
//         -1 if p0, p1, p2 are clockwise.
int orientation(point p0, point p1, point p2)
{
    // There is a possiblity that overflow occurs.
    long long det = (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x);
    if (det > 0) {
        return 1;
    }
    if (det < 0) {
        return -1;
    }
    return 0;
}

// Graham scan algorithm
vector<size_t> convex_hull(vector<point> &p, bool boundary_edges = false)
{
    if (p.size() <= 2) {
        return vector<size_t>();
    }
    // Compares p1, p2 with respect to their arguments.
    // If the arguments are the same, 
    // the nearer point is less the other.
    // p1.y >= 0 and p2.y >= 0.
    function<bool(point, point)> comp = [](point p1, point p2) -> bool {
        long long det = p1.x * p2.y - p1.y * p2.x;
        if (det > 0) {
            return true;
        }
        if (det == 0) {
            int d1 = p1.x * p1.x + p1.y * p1.y;
            int d2 = p2.x * p2.x + p2.y * p2.y;
            return d1 > d2;
        }
        return false;
    };
    size_t min_index = distance(p.begin(), min_element(p.begin(), p.end(), [](point p1, point p2) {
        if (p1.y < p2.y) {
            return true;
        } else if (p1.y == p2.y) {
            return p1.x < p2.x;
        }
        return false;
    }));
    vector<size_t> v;
    v.push_back(min_index);
    for (size_t i = 0; i < p.size(); ++i) {
        if (i != min_index) {
            v.push_back(i);
        }
    }
    sort(v.begin() + 1, v.end(), [&](size_t i1, size_t i2) {
        return comp(p[i1] - p[min_index], p[i2] - p[min_index]);
    });
    bool all_points_are_colinear;
    REP(i, v.size() - 2) {
        if (orientation(p[v[i]], p[v[i + 1]], p[v[i + 2]]) == 0) {
        } else {
            reverse(v.begin() + 1, v.begin() + i + 2);
            all_points_are_colinear = false;
            break;
        }
    }
    if (all_points_are_colinear) {
        return vector<size_t>();
    }
    stack<size_t> s;
    s.push(0);
    s.push(1);
    s.push(2);
    size_t prev, curr, next;
    while (true) {
        next = s.top(); s.pop();
        curr = s.top(); s.pop();
        prev = s.top(); s.pop();
        int ori = orientation(p[v[prev]], p[v[curr]], p[v[next]]);
        if (ori == 1 || boundary_edges && ori == 0) {
            s.push(prev);
            s.push(curr);
            s.push(next);
            if (next == p.size() - 1) {
                break;
            }
            s.push(next + 1);
        } else {
            s.push(prev);
            s.push(next);
        }
    }
    vector<size_t> w(s.size());
    int iter = s.size() - 1;
    while (!s.empty()) {
        w[iter--] = v[s.top()];
        s.pop();
    }
    return w;
}

int main(void)
{
    int n;
    cin >> n;
    vector<point> p;
    REP(i, n) {
        int x, y;
        cin >> x >> y;
        p.push_back(point(x, y));
    }
    vector<size_t> v = convex_hull(p, true);
    cout << v.size() << endl;
    REP(i, v.size()) {
        cout << p[v[i]].x << " " << p[v[i]].y << endl;
    }
    return 0;
}


Measured memory_bytes: 5268