#include <bits/stdc++.h>
#define SIZE 300005
#define MOD 1000000007LL
#define EPS 1e-10
#define INF 1 << 30
#define LLINF 1LL << 60
#define REP(i,n) for(int i=0;i<n;i++)
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define DOWN(i,b,a) for(int i=b;i>=a;i--)
#define SET(a,c) memset(a,c,sizeof a)
#define BIT(i,j) ((i)>>(j))&1
#define ALL(o) (o).begin(), (o).end()
#define ERASE(o) (o).erase(unique((o).begin(),(o).end()), (o).end())
#define SQ(x) ((x)*(x))
using namespace std;
typedef long long ll;
typedef pair<ll,ll> Pll;
typedef pair<int, int> Pii;
typedef pair<double, double> Pdd;
typedef complex<double> dcomplex;
template<typename T> inline void priv(vector<T>a){REP(i,a.size()){cerr<<a[i]<<((i==a.size()-1)?"\n":" ");}}
ll gcd(ll a,ll b){int c=max(a,b);int d=min(a,b);return c==0||d==0?c:gcd(c%d,d);}
ll lcm(ll a,ll b){return a==0||b==0?0:a*b/gcd(a,b);}
ll fact(ll a){ll b=1;FOR(i,1,a)b*=i;return b;}
ll power(ll x,ll n){ll a=1;REP(i,n)a*=x;return a;}

template<typename T> struct Edge
{
  int src, dst; T w;
  Edge() {};
  Edge(int src, int dst): src(src), dst(dst) {w=1;}
  Edge(int src, int dst, T w): src(src), dst(dst), w(w){}
  bool operator<(const Edge &e)const{return w != e.w ? w > e.w : dst < e.dst;}
  bool operator==(const Edge &e){return dst == e.dst;}
};

template<typename T> class Graph : public vector<vector<Edge<T>>>
{
private:
  pair<T,int> farthest(int p, int v)
  {
    pair<T,int> r(0,v);
    for(auto e : (*this)[v]) if(e.dst != p)
    {
      auto t = farthest(v,e.dst);
      t.first += e.w;
      if(t.first > r.first) r=t;
    }
    return r;
  }

  void backward(Graph &h, int v, int s, int r,
  vector<int> &no, vector<vector<int>> &cmp,
  vector<int> &prev, vector<vector<int>> &next, vector<T> &mcost,
  vector<int> &mark, T &cost, bool &found)
  {
    const int n = h.size();
    if(mark[v])
    {
      vector<int> tmp = no;
      found = true;
      do {
        cost += mcost[v];
        v = prev[v];
        if (v != s)
        {
          while (cmp[v].size() > 0)
          {
            no[cmp[v].back()] = s;
            cmp[s].push_back(cmp[v].back());
            cmp[v].pop_back();
          }
        }
      } while (v != s);
      for(auto j : cmp[s]) if(j!=r) for(int l=0;l<h[j].size();l++)
        if(no[h[j][l].src]!=s) h[j][l].w -= mcost[tmp[j]];
    }
    mark[v] = true;
    for(auto i : next[v]) if(no[i]!=no[v]&&prev[no[i]]==v)
      if (!mark[no[i]]||i==s)
        backward(h,i,s,r,no,cmp,prev,next,mcost,mark,cost,found);
  }

public:
  T inf;
  Graph(int n){(*this).resize(n);inf=numeric_limits<T>::max();}
  void direct(int s, int t){(*this)[s].push_back(Edge<T>(s,t));}
  void direct(int s, int t, T w){(*this)[s].push_back(Edge<T>(s,t,w));}
  void undirect(int s, int t){direct(s,t);direct(t,s);}
  void undirect(int s, int t, T w){direct(s,t,w);direct(t,s,w);}

  /*
    cost : O(|V|)
  */
  T diameter(void)
  {
    auto r = farthest(-1,0);
    auto t = farthest(-1,r.second);
    return t.first;
  }

  /*
    s : start point
    cost : O(|E|+|V|log(|V|))
    dist : distances
  */
  vector<T> dijkstra(int s = 0)
  {
    const int n = (*this).size();
    priority_queue<Edge<T>> PQ;
    vector<T> dist(n,inf);
    dist[s] = 0; Edge<T> e(-1,s,0);
    PQ.push(e);
    while(!PQ.empty())
    {
      auto f = PQ.top(); PQ.pop();
      int u = f.dst;
      if(dist[u] < f.w * (-1)) continue;
      for(int j=0;j<(*this)[u].size();j++)
      {
        int v = (*this)[u][j].dst;
        if(dist[v] > dist[u] + (*this)[u][j].w)
        {
          dist[v] = dist[u] + (*this)[u][j].w;
          Edge<T> e(-1,v,dist[v]*(-1));
          PQ.push(e);
        }
      }
    }
    return dist;
  }

  /*
    s : start point
    cost : O(|V||E|)
    first : is negative cycle ?
    second : distances
  */
  pair<bool,vector<T>> bellmanFord(int s = 0)
  {
    const int n = (*this).size();
    vector<T> dist(n,inf);
    dist[s] = 0;
    for(int i=0;i<n;i++)
    {
      bool update = false;
      for(int v=0;v<n;v++) for(auto e : (*this)[v])
      {
        if (dist[v] != inf && dist[e.dst] > dist[v] + e.w)
        {
          dist[e.dst] = dist[v] + e.w;
          update = true;
          if(i==n-1) return make_pair(true,dist);
        }
      }
      if(!update) break;
    }
    return make_pair(false,dist);
  }

  /*
    Minimum Spanning Tree (Prim's algorithm)
    r : root
    cost : O(|E|+|V|log|V|) (because priority_queue use paring sort)
    first : total cost
    second : edges
  */
  pair<T, vector<Edge<T>>> MST(int r = 0)
  {
    const int n = (*this).size();
    vector<Edge<T>> U;
    T total = 0;
    vector<bool> visited(n,false);
    priority_queue<Edge<T>> Q;
    Q.push(Edge<T>(-1,r,0));
    while (!Q.empty())
    {
      auto e = Q.top(); Q.pop();
      if (visited[e.dst]) continue;
      U.push_back(e);
      total += e.w;
      visited[e.dst] = true;
      for(auto f : (*this)[e.dst]) if (!visited[f.dst]) Q.push(f);
    }
    return make_pair(total, U);
  }

  /*
    Minimum Spanning Arborescence(Chu-Liu/Edmond)
    r : root
    cost : O(|V||E|)
  */
  T MSA(int r = 0) {
    const int n = (*this).size();
    Graph<T> h(n);
    for(int i=0;i<n;i++) for(auto e : (*this)[i]) h[e.dst].push_back(e);
    vector<int> no(n);
    vector< vector<int> > cmp(n);
    for(int i=0;i<n;i++) cmp[i].push_back(no[i]=i);
    for (T cost=0;;)
    {
      vector<int> prev(n,-1);
      vector<T> mcost(n,inf);
      for(int j=0;j<n;j++) if(j!=r) for(auto e : h[j])
        if (no[e.src] != no[j])
          if (e.w < mcost[no[j]])
            mcost[no[j]] = e.w, prev[no[j]] = no[e.src];
      vector<vector<int>> next(n);
      for(int i=0;i<n;i++) if(prev[i]>=0)
        next[prev[i]].push_back(i);
      bool stop = true;
      vector<int> mark(n);
      for(int i=0;i<n;i++) if(i!=r&&!mark[i]&&!cmp[i].empty())
      {
        bool found = false;
        backward(h, i, i, r, no, cmp, prev, next, mcost, mark, cost, found);
        if (found) stop = false;
      }
      if (stop)
      {
        for(int i=0;i<n;i++) if(prev[i] >= 0) cost+=mcost[i];
        return cost;
      }
    }
  }

  /*
    articulation points
    costs O(|E|log|V|)
  */
  void visit(int current, int prev, int &timer,
  vector<bool> &visited, vector<int> &prenum,
  vector<int> &parent, vector<int> &lowest)
  {
    prenum[current]=lowest[current]=timer++;
    visited[current]=true;
    for(auto e : (*this)[current])
    {
      int next = e.dst;
      if(!visited[next])
      {
        // cerr << e.src << " " << e.dst << endl;
        parent[next]=current;
        visit(next,current,timer,visited,prenum,parent,lowest);
        lowest[current]=min(lowest[current],lowest[next]);
      }
      else if(next != prev)
        lowest[current]=min(lowest[current],prenum[next]);
    }
  }

  set<int> APs(void)
  {
    const int n = (*this).size();
    set<int> ap;
    if(n==1) return ap;
    int timer = 1;
    vector<bool> visited(n,false);
    vector<int> prenum(n), parent(n), lowest(n);
    visit(0,-1,timer,visited,prenum,parent,lowest);
    int np = 0;
    for(int i=1; i<n; i++)
    {
      int p = parent[i];
      if(p==0) np++;
      else if(prenum[p]<=lowest[i]) ap.insert(p);
    }
    if(np > 1) ap.insert(0);
    return ap;
  }
};

int main()
{
  int v,e,r; cin>>v>>e;
  Graph<long long> g(v);
  REP(i,e)
  {
    ll s,t; cin >> s >> t;
    g.undirect(s,t);
  }
  auto ans = g.APs();
  for(auto x : ans) cout << x << endl;
  return 0;
}

Measured memory_bytes: 6028