#include using namespace std; using ll = long long; using ull = unsigned long long; #define rep(i,a,b) for (int i=a; i=b; i--) #define fore(i,a) for(auto &i:a) #define pb push_back #define _GLIBCXX_DEBUG #define All(a) (a).begin(), (a).end() #define YN(a) ((a) ? "Yes" : "No") template inline bool chmin(T& a, const T& b) {bool c=a>b; if(a>b) a=b; return c;} template inline bool chmax(T& a, const T& b) {bool c=a inline T gcd(T a,T b) {return (b==0)?a:gcd(b,a%b);} template inline T lcm(T a, T b) {return (a*b)/gcd(a,b);} const int inf = INT_MAX / 2; const ll infl = 1LL << 61; using vi = vector; using vvi = vector>; using vvvi = vector>>; using vll = vector; using vvll = vector>; using vvvll = vector>>; template void OUT(const T& x) { cout << x; } // ----- pair ----- template void OUT(const pair& p) { cout << "("; OUT(p.first); cout << ", "; OUT(p.second); cout << ")"; } // ----- vector ----- template void OUT(const vector& v) { cout << "[ "; for (const auto& x : v) { OUT(x); cout << " "; } cout << "]"; } // ----- set ----- template void OUT(const set& s) { cout << "{ "; for (const auto& x : s) { OUT(x); cout << " "; } cout << "}"; } // ----- map ----- template void OUT(const map& mp) { cout << "{ "; for (const auto& [k,v] : mp) { OUT(k); cout << ": "; OUT(v); cout << ", "; } cout << "}"; } // ----- queue ----- template void OUT(queue q) { cout << "< "; while (!q.empty()) { OUT(q.front()); cout << " "; q.pop(); } cout << ">"; } // ----- priority_queue ----- template void OUT(priority_queue pq) { cout << "< "; while (!pq.empty()) { OUT(pq.top()); cout << " "; pq.pop(); } cout << ">"; } // ----- stack ----- template void OUT(stack st) { cout << "< "; while (!st.empty()) { OUT(st.top()); cout << " "; st.pop(); } cout << ">"; } // ----- deque ----- template void OUT(const deque& dq) { cout << "[ "; for (const auto& x : dq) { OUT(x); cout << " "; } cout << "]"; } #define out(x) cerr << #x << ": ", OUT(x), cerr << '\n'; template // costの型 struct Graph { ///////////////辺の構造体↓///////////// struct Edge{ T c; int u,v; // u→vの辺 // 辺同士の比較は、c → u → vの順番に比較 bool operator<(const Edge &other) const { return tie(c,u,v) < tie(other.c, other.u, other.v); } }; /////////////メンバ変数↓/////////////// vector>> _nexts; // nexts[i] := i番目からの隣接リスト{コスト、行き先} vector _edges; // 辺リスト T _total_edge_cost; // 全ての辺のコストの合計 vector _deg_in; // 全ての頂点の入次数を保持 vector _deg_out; // 全ての頂点の出次数を保持 //////////コンストラクタ↓//////////// Graph(int n): _nexts(n), _total_edge_cost(0), _deg_in(n,0), _deg_out(n,0) {} //////////メンバ関数↓//////////////// //コスト1の辺u → vを追加する関数 void add_edge(int u, int v){ add_edge(T(1), u, v); } //コストcの辺u → vを追加する関数 void add_edge(T c, int u, int v){ _nexts[u].push_back({c, v}); _edges.push_back(Edge{c, u, v}); _total_edge_cost += c; _deg_out[u]++; _deg_in[v]++; } //頂点数を返す関数 int size_n() const { return _nexts.size(); } //辺の本数を返す関数 int size_e() const { return _edges.size(); } // 全ての辺のコストの合計を返す T total_edge_cost() const { return _total_edge_cost; } // 辺リストを返す関数 const vector& edges() const { return _edges; } // 隣接リストを返す関数 const vector>>& nexts() const { return _nexts; } // iから行ける場所の{コスト、行き先}の集合を返す関数 const vector>& nexts(int i) const { return _nexts[i]; } // 入次数配列を返す関数 const vector& deg_in() const { return _deg_in; } // 頂点iの入次数を返す関数 int deg_in(int i) const { return _deg_in[i]; } // 出次数配列を返す関数 const vector& deg_out() const { return _deg_out; } // 頂点iの出次数を返す関数 int deg_out(int i) const { return _deg_out[i]; } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n,m; cin >> n >> m; vector> degs(n); vi ans(n,-1); rep(i,0,n){ //出次数、頂点インデックス degs[i] = make_pair(0,i); } Graph G(n); rep(i,0,m){ int u,v; cin >> u >> v; u--; v--; degs[u].first ++; degs[v].first ++; G.add_edge(u,v); G.add_edge(v,u); } //切った頂点 いずれ逆順にする //vi cut(n-3,-1); stack cut; vi visited(n,0); //切った頂点の隣接頂点index //vvi cut_nx(n-3, vi(3,-1)); stack cut_nx; //colは1,2,3,4のどれかのみ vi col(n,0); //これから切る頂点を格納 queue q; rep(i,0,n){ //このiは真の頂点index if(G.deg_out()[i] == 3) q.push(i); } rep(i,0,n-3){ //out(q); //out(i); cut.push(q.front()); q.pop(); visited[cut.top()] = 1; //out(cut.top()); //頂点を削除するのは大変そうなので、visitedでやるしかないか vi vec; rep(j,0,G.deg_out(cut.top())){ int nv = G.nexts(cut.top())[j].second; if(!visited[nv]){ // 隣接頂点の格納 vec.pb(nv); degs[nv].first--; if(degs[nv].first == 3) q.push(nv); } } cut_nx.push(vec); } int c0 = 1; rep(i,0,n){ if(visited[i] == 0){ col[i] = c0; c0 ++; } } rep(i,0,n-3){ int cur = cut.top(); cut.pop(); vi vec = cut_nx.top(); cut_nx.pop(); rep(j,1,5){ bool flag = false; rep(k,0,3){ if(col[vec[k]] == j){ flag = true; break; } } if(flag) continue; col[cur] = j; break; } } cout << "Yes" << endl; rep(i,0,n){ cout << col[i] << " "; } cout << endl; cout << fixed << setprecision(30); return 0; }