#include <iostream>
#include <iomanip>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <list>
#include <bitset>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
 
using namespace std;
 
#define R 1000000007
#define M 998244353

long long mx = 0, mn = INT32_MAX;

struct maths_util
{
    vector<long long> fact;
    maths_util(int n = 200001, long long q = R)
    {
        fact.resize(n);
        fact[0] = 1;
        for(int i = 1; i < n; i++)
            fact[i] = (fact[i-1] * i) % q;
    }
    long long power_mod_q(long long x, long long n, long long q = R)
    {
        if(n == 0)
            return 1;
        if(n % 2 == 0)
            return power_mod_q((x * x) % q, n / 2, q);
        else 
            return (x * power_mod_q((x * x) % q, n / 2, q)) % q; 
    }

    long long inverse_mod_q(long long n, long long q = R)
    {
        return power_mod_q(n, q-2, q);
    }


    long long nCr_mod_q(long long n, long long r, long long q = R)
    {
        return (((fact[n] * inverse_mod_q(fact[r], q)) % q) * inverse_mod_q(fact[n-r], q)) % q;
    }

};

struct dsu
{
    vector<int> rank, parent;
    dsu(int n)
    {
        rank.assign(n, 0);
        parent.resize(n);
        for(int i = 0; i < n; i++)
            parent[i] = i;
    }
    int find_set(int i)
    {
        if(i == parent[i])
            return i;
        return parent[i] = find_set(parent[i]);
    }
    bool is_same_set(int i, int j)
    {
        if(find_set(i) == find_set(j))
            return true;
        return false;
    }
    void union_set(int i, int j)
    {
        if(!is_same_set(i, j))
        {
            int u = find_set(i), v = find_set(j);
            if(rank[u] == rank[v])
            {
                parent[v] = u;
                rank[u] += 1;
            }
            else if(rank[u] > rank[v])
                parent[v] = u;
            else 
                parent[u] = v;
        }
    }

};

struct graph
{
    int v, time, count;
    vector<pair<int, int>>* adj;
    vector<bool> visited, in_stack, ap;
    stack<int> s;
    vector<int> color, cc, d, f, l, finish_time, indegree, sccs, topo, outdegree, pred, child;
    graph(int n)
    {
        v = n;
        adj = new vector<pair<int, int>>[n];
        visited.assign(n, false);
        in_stack.assign(n, false);
        ap.assign(n, false);
        color.assign(n, 0);
        cc.assign(n, -1);
        d.assign(n, -1);
        f.assign(n, -1);
        indegree.assign(n, 0);
        outdegree.assign(n, 0);
        pred.assign(n, -1);
        child.assign(n, 0);
        time = count = 0;
        finish_time.assign(2 * n, -1);
        l.assign(n, 0);
    }
    void add_edge(int u, int v, int w)
    {
        adj[u].push_back(make_pair(v, w));
        // adj[v].push_back(make_pair(u, w));
    }

    void dfs()
    {
        color.assign(v, 0);
        cc.assign(v, -1);
        time = count = 0;
        finish_time.assign(2 * v, -1);
        visited.assign(v, false);
        pred.assign(v, -1);
        for(int i = 0; i < v; i++)
        {
            if(color[i] == 0)
            {
                dfs(i);
                count++;
            }
        }
        for(int i = 0; i < v; i++)
            finish_time[f[i]] = i;
    }
    void dfs(int i)
    {
        color[i] = 1;
        cc[i] = count;
        d[i] = time;
        time++;
        for(auto x : adj[i])
            if(color[x.first] == 0)
            {
                pred[x.first] = i;
                dfs(x.first);
            }
        color[i] = 2;
        f[i] = time;
        time++;
    }

    void scc()
    {
        graph g_t(v);
        for(int i = 0; i < v; i++)
            for(auto x : adj[i])
                g_t.add_edge(x.first, i, x.second);
        dfs();
        for(int i = 2 * v - 1; i >= 0; i--)
        {
            if(finish_time[i] != -1 && g_t.color[finish_time[i]] == 0)
            {
                g_t.dfs(finish_time[i]);
                g_t.count++;
            }
        }
        sccs = g_t.cc;
        count = g_t.count;
    }

    void topological_sort()
    {
        for(int i = 0; i < v; i++)
            if(adj[i].size())
                outdegree[i] = adj[i].size();
        for(int i = 0; i < v; i++)
            for(auto x : adj[i])
                indegree[x.first]++;
        vector<int> zero;
        for(int i = 0; i < v; i++)
            if(!indegree[i])
                zero.push_back(i);
        
        while(!zero.empty())
        {
            int v = zero.back();
            topo.push_back(v);
            zero.pop_back();
            for(auto x : adj[v])
            {
                indegree[x.first]--;
                if(!indegree[x.first])
                    zero.push_back(x.first);
            }
        }
    }

    void tarjan()
    {
        for(int i = 0; i < v; i++)
            if(!color[i])
                tarjan(i);
    }
    void tarjan(int i)
    {
        color[i] = 1;
        d[i] = time;
        time++;
        l[i] = d[i];
        s.push(i);
        in_stack[i] = true;
        for(auto x : adj[i])
        {
            if(!color[x.first])
            {
                tarjan(x.first);
                l[i] = min(l[i], l[x.first]);
            }
            else if(in_stack[x.first])
            {
                l[i] = min(l[i], d[x.first]);
            }
        }
        
        color[i] = 2;
        f[i] = time;
        time++;
        if(d[i] == l[i])
        {
            while(s.top() != i)
                cout << s.top() << ' ', in_stack[s.top()] = false, s.pop();
            cout << s.top() << '\n', in_stack[s.top()] = false, s.pop();
        }
    }

    void articulate()
    {
        ap.assign(v, false);
        color.assign(v, 0);
        child.assign(v, 0);
        time = 0;
        d.assign(v, -1);
        f.assign(v, -1);
        l.assign(v, 0);
        pred.assign(v, -1);
        for(int i = 0; i < v; i++)
            if(color[i] == 0)
            {
                articulate(i);
                if(child[i] == 1 || child[i] == 0)
                    ap[i] = false;
            }
    }

    void articulate(int i)
    {
        color[i] = 1;
        d[i] = time;
        time++;
        l[i] = d[i];
        for(auto x : adj[i])
        {
            if(pred[i] != x.first && color[x.first] == 1)
                l[i] = min(l[i], d[x.first]);
            if(!color[x.first])
            {
                pred[x.first] = i;
                child[i]++;
                articulate(x.first);
                l[i] = min(l[i], l[x.first]);
            }
            if(l[x.first] >= d[i])
                ap[i] = true;
            
        } 
        f[i] = time;
        time++;
        color[i] = 2;
    }

    void bridges()
    {
        time = 0;
        f.assign(v, -1);
        d.assign(v, -1);
        l.assign(v, 0);
        color.assign(v, 0);
        pred.assign(v, -1);
        for(int i = 0; i < v; i++)
            if(color[i] == 0)
                bridge(i);
    }

    void bridge(int i)
    {
        color[i] = 1;
        d[i] = time;
        time++;
        l[i] = d[i];
        for(auto itr = adj[i].begin(); itr != adj[i].end(); itr++)
        {
            if(pred[i] != itr -> first && color[itr -> first] == 1)
                l[i] = min(l[i], d[itr -> first]);
            if(!color[itr -> first])
            {
                pred[itr -> first] = i;
                bridge(itr -> first);
                l[i] = min(l[i], l[itr -> first]);
            }
            if(l[itr -> first] > d[i])
                itr -> second = 1;
        }
        color[i] = 2;
        f[i] = time;
        time++;
    }
};

struct kmp
{
    vector<int> lps;
    string p, t;
    kmp(string& p, string& t)
    {
        this -> p = p;
        this -> t = t;
        //PREPROCESSING
        lps.assign(p.size(), 0);
        lps[0] = 0;
        int len = 0, i = 1, j;
        while(i < p.size())
        {
            if(p[i] == p[len])
                lps[i++] = ++len;
            else if(len)
                len = lps[len-1];
            else 
                lps[i++] = 0;
        }
        //STRING MATCHING
        i = j = 0;
        while(i < t.size())
        {
            if(p[j] == t[i])
                j++, i++;
            
            if(j == p.size())
                j = lps[j-1];
            else if(i < t.size() && p[j] != t[i])
            {
                if(j)
                    j = lps[j-1];
                else 
                    i++;
            }
        }

        
    }
};

struct segment_tree
{
    int n;
    vector<int> a;
    vector<int> tree;
    segment_tree(vector<int>& a)
    {
        n = a.size();
        this -> a = a;
        tree.resize(4 * n);
        build(0, 0, n - 1);
    }
    void build(int node, int l, int r)
    {
        if(l == r)
            tree[node] = a[l] = a[r];
        else
        {
            int mid = (l + r) / 2;
            build(2 * node + 1, l, mid);
            build(2 * node + 2, mid + 1, r);
            tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
        }
    }
    void update(int node, int l, int r, int i, char x)
    {
        if(l == r)
        {
            a[l] = a[r] = x;
            tree[node] = a[l] = a[r];
        }
        else
        {
            int mid = (l + r) / 2;
            if(i >= l && i <= mid)
                update(2 * node + 1, l, mid, i, x);
            else
                update(2 * node + 2, mid + 1, r, i, x);
            tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
        }
    }
    int query(int node, int l, int r, int s, int e)
    {
        if(l == s && r == e)
            return tree[node];
        else
        {
            int mid = (s + e) / 2;
            if(r <= mid)
                return query(2 * node + 1, l, r, s, mid);
            else if(l > mid)
                return query(2 * node + 2, l, r, mid + 1, e);
            else 
                return query(2 * node + 1, l, mid, s, mid) + query(2 * node + 2, mid + 1, r, mid + 1, e);
        }
    }
};


int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, m, u, v;
    cin >> n >> m;

    graph g(n);

    while(m--)
        cin >> u >> v, g.add_edge(u - 1, v - 1, 1);
    
    g.topological_sort();

    // for(auto x : g.topo)
    //     cout << x << ' ';
    // cout << '\n';

    vector<long long> dp(n, 0);
    
    map<int, int> pos;
    for(int i = 0; i < n; i++)
        pos[g.topo[i]] = i;
    
    for(int i = n - 2; i >= 0; i--)
    {
        mx = 0;
        for(auto x : g.adj[g.topo[i]])
        {
            mx = max(mx, dp[pos[x.first]]);
            dp[i] = mx + 1;
        }
    }
    mx = 0;
    for(int i = 0; i < n; i++)
        mx = max(mx, dp[i]);
    cout << mx << '\n';

    // for(int i = 1; i)
    // int k, q;
    // cin >> k >> q;
    // vector<long long> d(k), d_sum(k);
    // for(int i = 0; i < k; i++)
    //     cin >> d[i];
    // d_sum[0] = d[0];
    // for(int i = 1; i < k; i++)
    //     d_sum[i] = d_sum[i-1] + d[i];
    // while(q--)
    // {
    //     int n, x, m, z = 0;
    //     long long ans = 0;
    //     cin >> n >> x >> m;
    //     vector<long long> d_mod_m(k), a(k + 1);
    //     for(int i = 0; i < k; i++)
    //         d_mod_m[i] = d[i] % m;
        
    //     for(int i = 0; i < k; i++)
    //         if(d_mod_m[i] == 0)
    //             z++;
    //     ans = ans + (n / k) * z;
    //     for(int i = 0; i < (n % k); i++)
    //         if(d_mod_m[i] == 0)
    //             ans++;
        
    //     a[0] = x % m;
    //     for(int i = 0; i < k; i++)
    //         a[i + 1] = (x + d_sum[i]) / m;


    //     for(int i = 0; i < k; i++)
    //         cout << d_mod_m[i] << ' ';
    //     cout << '\n';

    //     for(auto x : a)
    //         cout << x << ' ';
    //     cout << '\n';

    // }
    return 0;
}

Measured memory_bytes: 15992