#include <bits/stdc++.h>
using namespace std;

template<class VData, class EData, class GData>
struct Graph;

struct DefVData {};

struct DefEData {
  int len;
  DefEData() : len(1) {}
};

template<class VData=DefVData, class EData=DefEData, class GData=int>
struct SCCVData {
  Graph<VData, EData, GData> g;
  vector<int> cont2orig; // v on g -> cont2orig[v] on the original graph(G)
  SCCVData(int n) : g(n) {}
};

// the actual edge(before contracted) is s -> t
struct SCCEData {
  int len;
  int s;
  int t;
  SCCEData() : len(1) {}
  SCCEData(int a, int b) : len(1), s(a), t(b) {}
};

struct SCCGData {
  vector<int> orig2cont; // v on G -> orig2cont[v] on G.SCC.vdat[belong[v]].g
};

template<class VData=DefVData, class EData=DefEData, class GData=int>
struct Graph {
  struct Edge {
    int to;
    Edge *rev;
    EData dat;
  };
  using Len = decltype(EData::len);
  using Ls = vector<Len>;
  using Es = vector<Edge>;
  using Brid = pair<int, Edge*>;
  Len INF;

  int n;
  int grp; // the number of SCC
  GData gdat;
  vector<VData> vdat;
  vector<Es> es;
  vector<Es> res;
  vector<Ls> cost;
  vector<int> ord;
  vector<int> low;
  vector<int> belong;
  vector<int> artic;
  vector<Brid> bridge;
  
  Graph() {}
  Graph(int sz, Len inf) {
    Init(sz, inf);
  }

  void Init(int sz, Len inf) {
    INF = inf;
    n = sz;
    vdat.resize(sz);
    es.resize(sz);
    res.resize(sz);
  }

  void AddEdge(int u, int v, EData d, EData r) {
    es[u].emplace_back(Edge{v, NULL, d});
    res[v].emplace_back(Edge{u, NULL, r});
    es[u].back().rev = &res[v].back();
    res[v].back().rev = &es[u].back();
  }

  void AddEdge(int u, int v, EData d=EData()) {
    AddEdge(u, v, d, d);
  }

  // should be tested
  template<class Cmp>
  void EraseMultipleEdge(Cmp cmp, bool really_erase) {
    auto same = [](const Edge &a, const Edge &b) {
      return a.to == b.to;
    };

    for (auto ves : es) {
      sort(ves.begin(), ves.end(), cmp);
      auto itr = unique(ves.begin(), ves.end(), same);
      if (really_erase) ves.erase(itr, ves.end());
    }

    for (auto ves : res) {
      sort(ves.begin(), ves.end(), cmp);
      auto itr = unique(ves.begin(), ves.end(), same);
      if (really_erase) ves.erase(itr, ves.end());
    }
  }

  void CreateDFSForest(bool is_directed) {
    int k = 0;
    int idx = 0;
    vector<int> st(n);

    grp = 0;
    ord.resize(n, -1);
    low.resize(n);
    belong.resize(n, -1);

    function<void(int,int)> dfs;
    auto update = [&](int v, Edge &e, bool &is_artic, int &cnt) {
      int u = e.to;
      if (ord[u] != -1) {
        if (belong[u] == -1) low[v] = min(low[v], ord[u]);
      } else {
        cnt++;
        dfs(u, v);
        low[v] = min(low[v], low[u]);
        if (ord[v] <= low[u]) is_artic = true;
        if (ord[v] < low[u]) bridge.emplace_back(Brid(v, &e));
      }
    };

    dfs = [&](int v, int p) {
      bool is_artic = false;
      int cnt = 0;

      st[idx++] = v;
      low[v] = ord[v] = k++;

      for (auto e : es[v]) update(v, e, is_artic, cnt);
      if (!is_directed) {
        for (auto e : res[v]) update(v, e, is_artic, cnt);
      }

      if (p == v) is_artic = (cnt > 1);
      if (is_artic) artic.emplace_back(v);

      if (low[v] != ord[v]) return;
      while (1) {
        int u = st[--idx];
        belong[u] = grp;
        if (u == v) break;
      }
      grp++;
    };

    for (int i=0; i<n; i++) {
      if (ord[i] == -1) dfs(i, i);
    }
  }

  using DE = SCCEData;
  using G = Graph<SCCVData<VData, EData, GData>, DE, SCCGData>;
  G CreateSCC() {
    if (belong.empty()) CreateDFSForest(true);

    G ret(grp);
    ret.gdat.orig2cont.resize(n);
    for (int v=0; v<n; v++) {
      auto &vd = ret.vdat[belong[v]];
      ret.gdat.orig2cont[v] = ret.vd.cont2orig.size();
      vd.cont2orig.emplace_back(v);
    }

    for (int i=0; i<grp; i++) {
      auto &vd = ret.vdat[i];
      vd.g.Init(vd.cont2orig.size(), 0x33433433);
    }
    
    for (int v=0; v<n; v++) {
      for (auto e : es[v]) {
        int u = e.to;

        if (belong[v] == belong[v]) {
          auto &vd = ret.vdat[belong[v]];
          auto &dic = ret.gdat.orig2cont;
          vd.g.AddEdge(dic[v], dic[u], e.dat, e.rev->dat);
        } else {
          ret.AddEdge(belong[v], belong[u], DE(v, u), DE(u, v));
        }
      }
    }

    return ret;
  }

  void CreateAdjMat() {
    cost.clear();
    cost.resize(n, Ls(n, INF));

    auto cmp = [](const Edge &a, const Edge &b) {
      if (a.to != b.to) return a.to < b.to;
      return a.dat.len < b.dat.len;
    };
    EraseMultipleEdge(cmp, false);

    for (int i=0; i<n; i++) {
      int prev = -1;
      for (auto e : es[i]) {
        int v = e.to;
        if (prev > v) break;
        cost[i][v] = min(cost[i][v], e.dat.len);
        prev = v;
      }
    }
  }

  void DijkstraV(vector<int> &starts, Ls &d) {
    d.clear();
    d.resize(n, INF);
    for (int s : starts) d[s] = 0;

    if (cost.empty()) CreateAdjMat();

    vector<bool> used(n, false);
    for (int i=0; i<n; i++) {
      int v = -1;
      for (int u=0; u<n; u++) {
        if (used[u]) continue;
        if (v == -1 || d[u] < d[v]) {
          v = u;
        }
      }
      if (v == -1) return;

      used[v] = true;
      for (int u=0; u<n; u++) {
        d[u] = mind(d[u], d[v] + cost[v][u]);
      }
    }

    // must not reach here
    assert(0);
  }

  void DijkstraE(vector<int> &starts, Ls &d) {
    using Pair = pair<Len, int>;
    priority_queue<Pair, vector<Pair>, greater<Pair> > q;
    d.clear();
    d.resize(n, INF);
    for (int s : starts) d[s] = 0;

    while (!q.empty()) {
      Pair p = q.top(); q.pop();
      int v = p.second;
      if (d[v] < p.first) continue;
      for (auto e : es[v]) {
        int u = e.to;
        if (d[u] > d[v] + e.dat.len) {
          d[u] = d[v] + e.dat.len;
          q.push(Pair(d[u], u));
        }
      }
    }
  }

  // return true if the graph has a negative cycle
  bool SPFA(vector<int> &starts, Ls &d) {
    d.clear();
    d.resize(n, INF);

    queue<int> q;
    vector<bool> inq(n);
    vector<int> cnt(n);
    for (int s : starts) {
      inq[s] = true;
      q.push(s);
      d[s] = 0;
    }

    while (!q.empty()) {
      int v = q.front(); q.pop();
      inq[v] = false;
      cnt[v]++;
      if (cnt[v] > n) continue;
      for (auto e : es[v]) {
        int u = e.to;
        if (d[u] > d[v] + e.to) {
          d[u] = d[v] + e.to;
          if (inq[u]) continue;
          inq[u] = true;
          q.push(u);
        }
      }
    }

    // do DFS if you need the concrete contents of such cycles
    for (int i=0; i<n; i++) {
      if (cnt[i] >= n) return true;
    }

    return false;
  }

  void DijkstraV(int s, Ls &d) {
    vector<int> ss(1, s);
    DijkstraV(ss, d);
  }

  void DijkstraE(int s, Ls &d) {
    vector<int> ss(1, s);
    DijkstraE(ss, d);
  }

  void SPFA(int s, Ls &d) {
    vector<int> ss(1, s);
    SPFA(ss, d);
  }

  // Create "Shortest Path Graph", which should be DAG
  Graph<VData, EData, GData> RestoreShortestPaths(Ls &d) {
    Graph<VData, EData, GData> ret(n);

    for (int i=0; i<n; i++) {
      for (auto e : es[i]) {
        int v = e.to;
        if (d[v] == d[i] + e.dat.len) {
          ret.AddEdge(i, e.to, e.dat, e.rev->dat);
        }
      }
    }

    return ret;
  }

  void WarshallFloyd(vector<Ls> &ds) {
    if (cost.empty()) CreateAdjMat();

    ds.clear();
    ds.resize(cost);
    for (int i=0; i<n; i++) {
      ds[i][i] = min(ds[i][i], 0);
    }

    for (int k=0; k<n; k++) {
      for (int i=0; i<n; i++) {
        for (int j=0; j<n; j++) {
          if (ds[i][j] > ds[i][k] + ds[k][j]) {
            ds[i][j] = ds[i][k] + ds[k][j];
          }
        }
      }
    }
  }

  // Karp's Algorithm O(NM)
  // should be tested
  // should be used for SCC
  double MinimumMeanCycle() {
    vector<Ls> dp(n+1, Ls(n, INF));

    dp[0][0] = 0;
    for (int i=1; i<=n; i++) {
      for (int v=0; v<n; v++) {
        for (auto e : es[v]) {
          int u = e.to;
          dp[i][u] = min(dp[i][u], dp[i-1][v] + e.dat.len);
        }
      }
    }

    double ret = 1.0/0.0;
    for (int v=0; v<n; v++) {
      double alpha = (-1.0)/0.0;
      for (int i=1; i<n; i++) {
        if (dp[n][v] == INF) continue;
        if (dp[i][v] == INF) continue;
        alpha = max(alpha, (dp[n][v]-dp[i][v])/double(n-i));
      }
      ret = min(ret, alpha);
    }
    return ret;
  }
};

// will be edited later
template<class Len>
struct Tree {
  struct Edge {
    int to;
    Len len;
  };
  using Ls = vector<Len>;
  using Es = vector<Edge>;
  Len INF;

  int n;
  vector<Es> es;

  Tree(int sz, Len inf) : INF(inf) {}

  Len GetTreeDiameter() {
    using Pair = pair<Len, int>;
    auto dfs = [&](int v, int p) {
      Pair ret{0, v};
      for (auto e : es[v]) {
        if (e.to == p) continue;
        Pair p = dfs(e.to, v);
        p.first += e.len;
        ret = max(ret, p);
      }
      return ret;
    };

    Pair p = dfs(0, 0);
    return dfs(p.second, p.second).first;
  }
};

int n;
int m;

int main() {
  scanf("%d%d", &n, &m);
  Graph<> g(n, 0x33433433);

  while (m--) {
    int s, t;
    scanf("%d%d", &s, &t);
    g.AddEdge(s, t);
  }

  g.CreateDFSForest(false);
  sort(g.artic.begin(), g.artic.end());
  for (int v : g.artic) printf("%d\n", v);
}

Measured memory_bytes: 8552