// #define _GLIBCXX_DEBUG #include // clang-format off std::ostream&operator<<(std::ostream&os,std::int8_t x){return os<<(int)x;} std::ostream&operator<<(std::ostream&os,std::uint8_t x){return os<<(int)x;} std::ostream&operator<<(std::ostream&os,const __int128_t &v){if(!v)os<<"0";__int128_t tmp=v<0?(os<<"-",-v):v;std::string s;while(tmp)s+='0'+(tmp%10),tmp/=10;return std::reverse(s.begin(),s.end()),os< struct SetPowerSeries { #define SUBSET_REP(i, j, n) \ for (int _= (n); _>>= 1;) \ for (int __= 0, _2= _ << 1; __ < (n); __+= _2) \ for (int j= __, i= j | _, ___= i; j < ___; ++j, ++i) template static inline void ranked_zeta_tr(const T f[], T ret[][MAX_N + 1], const int sz) { for (int S= sz, c; S--;) ret[S][c= __builtin_popcount(S)]= f[S], std::fill_n(ret[S], c, 0); SUBSET_REP(S, U, sz) for (int d= __builtin_popcount(S); d--;) ret[S][d]+= ret[U][d]; } template static inline void conv_na(const T f[], const T g[], T ret[], const int sz) { for (int s= sz, t; s--;) for (ret[t= s]= f[s] * g[0]; t; --t&= s) ret[s]+= f[s ^ t] * g[t]; } template static inline void conv_tr(const T f[], const T g[], T ret[], const int sz) { static T F[1 << MAX_N][MAX_N + 1], G[1 << MAX_N][MAX_N + 1]; T tmp[MAX_N + 1]; ranked_zeta_tr(f, F, sz), ranked_zeta_tr(g, G, sz); const int n= __builtin_ctz(sz); for (int S= sz, c, d, e, bg; S--;) { c= __builtin_popcount(S), bg= std::min(2 * c, n); for (d= bg; d >= c; d--) for (tmp[d]= 0, e= d - c; e <= c; ++e) tmp[d]+= F[S][e] * G[S][d - e]; for (d= bg; d >= c; d--) F[S][d]= tmp[d]; } SUBSET_REP(S, U, sz) for (int c= __builtin_popcount(U), d= std::min(2 * c, n); d > c; d--) F[S][d]-= F[U][d]; for (int S= sz; S--;) ret[S]= F[S][__builtin_popcount(S)]; } template static inline void onconv_na(const T g[], T ret[], const F &phi, const int sz) { for (int s= 1, t; s < sz; phi(s, ret[s]), ++s) for (ret[t= s]= 0; t; --t&= s) ret[s]+= ret[s ^ t] * g[t]; } template static inline void onconv_tr(const T g[], T ret[], const F &phi, const int sz) { static T G[1 << MAX_N][MAX_N + 1], mat[MAX_N + 1][1 << MAX_N]; const int n= __builtin_ctz(sz); ranked_zeta_tr(g, G, sz), std::fill_n(mat[0], sz, ret[0]); for (int d= n; d; d--) std::fill_n(mat[d], sz, 0); for (int I= sz; I>>= 1;) phi(I, mat[1][I]= ret[0] * g[I]); for (int d= 2; d <= n; ++d) { SUBSET_REP(S, U, sz) mat[d - 1][S]+= mat[d - 1][U]; for (int S= sz; S--;) if (int c= __builtin_popcount(S); c <= d && d <= 2 * c) for (int e= d; e--;) mat[d][S]+= mat[e][S] * G[S][d - e]; SUBSET_REP(S, U, sz) mat[d][S]-= mat[d][U]; for (int S= sz; S--;) __builtin_popcount(S) == d ? phi(S, mat[d][S]), 0 : (mat[d][S]= 0); } for (int S= sz; --S;) ret[S]= mat[__builtin_popcount(S)][S]; } public: template // O(n 2^n) static inline void subset_sum(std::vector &f) { SUBSET_REP(S, U, f.size()) f[S]+= f[U]; } template // O(n 2^n) static inline void subset_sum_inv(std::vector &f) { SUBSET_REP(S, U, f.size()) f[S]-= f[U]; } template // O(n^2 2^n) static inline std::vector convolve(const std::vector &f, const std::vector &g) { const int sz= f.size(), n= __builtin_ctz(sz); std::vector ret(sz); if (n <= 10) return conv_na(f.data(), g.data(), ret.data(), sz), ret; assert(sz == 1 << n && sz == g.size()); return conv_tr(f.data(), g.data(), ret.data(), sz), ret; } // f(S) = φ_S ( Σ_{T⊊S} f(T)g(S/T) ) template // O(n^2 2^n) static inline std::vector semi_relaxed_convolve( const std::vector &g, T init, const F &phi= [](int, T &) {}) { const int sz= g.size(), n= __builtin_ctz(sz); std::vector ret(sz); ret[0]= init; if (n <= 12) return onconv_na(g.data(), ret.data(), phi, sz), ret; assert(sz == 1 << n); return onconv_tr(g.data(), ret.data(), phi, sz), ret; } // f(S) = φ_S ( Σ_{∅≠T⊊S & (T<(S/T) as binary numbers) } f(T)f(S/T) ) template // O(n^2 2^n) static inline std::vector self_relaxed_convolve(int n, const F &phi) { assert(__builtin_popcount(n) == 1); int I= 1, ed= std::min(1 << 13, n); std::vector ret(n, 0); for (int s, t, u= 1; I < ed; I<<= 1) for (t= s= 0; s < I; phi(u, ret[u]), t= ++s, ++u) for (ret[u]= 0; t; --t&= s) ret[u]+= ret[u ^ t] * ret[t]; T *h= ret.data(); for (; I < n; I<<= 1) phi(I, ret[I]), onconv_tr( h, h + I, [&](int s, T &x) { phi(s | I, x); }, I); return ret; } // F(f) : F[i] is coefficient of EGF ( = F^{(i)}(0) ) // "f[∅] = 0" is required. template // O(n^2 2^n) static inline std::vector composite(const std::vector &f, const EGF &F) { const int sz= f.size(), m= __builtin_ctz(sz), sz2= sz >> 1; assert(sz == 1 << m), assert(f.at(0) == 0); std::vector ret(sz); T *h= ret.data() + sz; const T *g= f.data(); for (int i= 0; i <= m; ++i) ret[sz - (1 << i)]= F[m - i]; int l= 1, ed= std::min(sz, 1 << 11), j; for (; l < ed; l<<= 1) for (j= sz2; j >= l; j>>= 1) conv_na(h - j, g + l, h - j - j + l, l); for (; l < sz; l<<= 1) for (j= sz2; j >= l; j>>= 1) conv_tr(h - j, g + l, h - j - j + l, l); return ret; } // exp(f) : "f[∅] = 0" is required. template // O(n^2 2^n) static inline std::vector exp(const std::vector &f) { const int sz= f.size(); assert(!(sz & (sz - 1))), assert(f.at(0) == 0); T h[sz]; const T *g= f.data(); int l= 1, ed= std::min(sz, 1 << 11); for (h[0]= 1; l < ed; l<<= 1) conv_na(h, g + l, h + l, l); for (; l < sz; l<<= 1) conv_tr(h, g + l, h + l, l); return std::vector(h, h + sz); } // log(f) : "f[∅] = 1" is required. template // O(n^2 2^n) static inline std::vector log(const std::vector &f) { const int sz= f.size(); assert(!(sz & (sz - 1))), assert(f.at(0) == T(1)); int I= 2, ed= std::min(sz, 1 << 13); T h[sz]; const T *g= f.data(); for (std::copy_n(g, ed, h); I < ed; I<<= 1) for (int s= 1, u= s | I; s < I; ++s, ++u) for (int t= s; t; --t&= s) h[u]-= h[u ^ t] * f[t]; for (; I < sz; I<<= 1) h[I]= g[I], onconv_tr( g, h + I, [&](int s, T &x) { x= g[I | s] - x; }, I); return h[0]= 0, std::vector(h, h + sz); } // f^k template // O(n^2 2^n) static inline std::vector pow(std::vector f, uint64_t k) { const int sz= f.size(), n= __builtin_ctz(sz); assert(sz == 1 << n); T F[MAX_N + 1]= {1}, pw= 1, bs= f[0]; int i= 1, ed= std::min(n, k); for (; i <= ed; ++i) F[i]= F[i - 1] * (k - i + 1); for (auto e= k - --i; e; e>>= 1, bs*= bs) if (e & 1) pw*= bs; for (; i >= 0; --i, pw*= f[0]) F[i]*= pw; return f[0]= 0, composite(f, F); } // P(f), P is polynomial template static inline std::vector polynomial_composite(std::vector f, std::vector P) { const int sz= f.size(), n= __builtin_ctz(sz); assert(sz == 1 << n); T F[MAX_N + 1]= {}; int e= P.size(); if (!e) return std::vector(sz); for (int j= 0;; ++j, --e) { for (int i= e; i--;) (F[j]*= f[0])+= P[i]; if (j == n || e == 1) break; for (int i= 1; i < e; ++i) P[i - 1]= P[i] * i; } return f[0]= 0, composite(f, F); } // {[X^{[n]}]f^k/k!} for k=0,1,...,n template // O(n^2 2^n) static inline std::vector egf(std::vector f) { static constexpr int M= 1 << 11; const int sz= f.size(), n= __builtin_ctz(sz), sz4= sz >> 2; assert(sz == 1 << n); if (n == 1) return {0, f[1]}; int l= sz4, m; T *in= f.data() + l, *dp= in + l, tmp[sz4 / 2], *dp2; for (int s; l > M; conv_tr(dp, in, dp, l), in-= (l>>= 1)) for (m= sz4; dp2= dp + (m - l), m > l; m>>= 1) for (s= l, conv_tr(dp2 + m - l, in, tmp, l); s--;) dp2[s]+= tmp[s]; for (int s; l; conv_na(dp, in, dp, l), in-= (l>>= 1)) for (m= sz4; dp2= dp + (m - l), m > l; m>>= 1) for (s= l, conv_na(dp2 + m - l, in, tmp, l); s--;) dp2[s]+= tmp[s]; std::vector ret(n + 1, 0); for (int i= n + 1; --i;) ret[i]= dp[(1 << (n - i)) - 1]; return ret; } // {[X^{[n]}]g*f^k/k!} for k=0,1,...,n template // O(n^2 2^n) static inline std::vector egf(const std::vector &f, std::vector g) { static constexpr int M= 1 << 11; const int sz= f.size(), n= __builtin_ctz(sz), sz2= sz >> 1, sz4= sz >> 2; assert(sz == 1 << n), assert(sz == (int)g.size()); if (n == 1) return {g[1], f[1] * g[0] + f[0] * g[1]}; int l= sz2, m; const T *in= f.data() + sz2; T *dp= g.data(), tmp[sz2 / 2], *dp2; for (int s; l > M; conv_tr(dp, in, dp, l), in-= (l>>= 1)) for (m= sz2; dp2= dp + (m - l), m > l; m>>= 1) for (s= l, conv_tr(dp2 + m - l, in, tmp, l); s--;) dp2[s]+= tmp[s]; for (int s; l; conv_na(dp, in, dp, l), in-= (l>>= 1)) for (m= sz2; dp2= dp + (m - l), m > l; m>>= 1) for (s= l, conv_na(dp2 + m - l, in, tmp, l); s--;) dp2[s]+= tmp[s]; std::vector ret(n + 1); for (int i= n + 1; i--;) ret[i]= dp[(1 << (n - i)) - 1]; return ret; } #undef SUBSET_REP }; template class UndirectedGraphSetPowerSeries { using SPS= SetPowerSeries; template using sps= std::vector; template using poly= std::vector; const unsigned V, sz; unsigned adj[MAX_V][MAX_V]= {0}, edge[MAX_V]= {0}; template static inline T pow(T x, int k) { for (T ret(1);; x*= x) if (k & 1 ? ret*= x : 0; !(k>>= 1)) return ret; } template inline void bfs(int s, const F &f) const { for (int t= s, u, j; t;) for (f(u= 1 << __builtin_ctz(t)); u;) j= __builtin_ctz(u), t^= 1 << j, u^= 1 << j, u|= edge[j] & t; } public: UndirectedGraphSetPowerSeries(int n): V(n), sz(1 << V) {} UndirectedGraphSetPowerSeries(const std::vector> &g): V(g.size()), sz(1 << V) { for (int i= V; i--;) for (int j= i; j--;) assert(g[i][j] == g[j][i]); for (int i= V; i--;) for (int j= V; j--;) adj[i][j]= g[i][j]; for (int i= V; i--;) for (int j= V; j--;) edge[i]|= !(!(adj[i][j])) << j; } int *operator[](int u) const { return adj[u]; } void add_edge(int u, int v, int cnt= 1) { adj[u][v]= (adj[v][u]+= cnt), edge[u]|= (1 << v), edge[v]|= (1 << u); if (!(adj[u][v])) edge[u]^= (1 << v), edge[v]^= (1 << u); } template static inline sps space_size(const sps &rank) { sps ret(rank.size()); for (int s= rank.size(); s--;) ret[s]= pow(2, rank[s]); return ret; } template static inline void transform(sps &f, const G &g) { const int sz2= f.size() / 2; sps tmp(sz2); for (int I= sz2; I; I>>= 1) { for (int t= 0; t < sz2; t+= I) for (int u= I, t2= t << 1; u--;) tmp[t | u]= f[t2 | I | u]; tmp= g(tmp); for (int t= 0; t < sz2; t+= I) for (int u= I, t2= t << 1; u--;) f[t2 | I | u]= tmp[t | u]; } } template // O(V^3 2^V) static inline void connect_to_biconnect(sps &f) { transform(f, SPS::template log); } template // O(V^3 2^V) static inline void biconnect_to_connect(sps &f) { transform(f, SPS::template exp); } template // O(V 2^V) inline void loop_ignored_to_loop_permitted(sps &f) const { auto tmp= space_size(loop_size()); for (int s= sz; s--;) f[s]*= tmp[s]; } inline sps edge_space_rank() const { // O(V 2^V) sps ret(sz, 0); for (int i= V; i--;) for (int j= i + 1; j--;) ret[(1 << i) | (1 << j)]= adj[i][j]; return SPS::subset_sum(ret), ret; } inline sps loop_size() const { // O(V 2^V) sps ret(sz, 0); for (int i= V; i--;) ret[(1 << i)]= adj[i][i]; return SPS::subset_sum(ret), ret; } inline sps loop_ignored_edge_space_rank() const { // O(V 2^V) sps ret(sz, 0); for (int i= V; i--;) for (int j= i; j--;) ret[(1 << i) | (1 << j)]= adj[i][j]; return SPS::subset_sum(ret), ret; } inline sps connected_component_num() const { // O(V 2^V) sps ret(sz, 0); for (int s= sz; s--;) bfs(s, [&](int) { ret[s]++; }); return ret; } inline sps cycle_space_rank() const { // O(V 2^V) sps e= edge_space_rank(), k= connected_component_num(), ret(sz, 0); for (int s= sz; s--;) ret[s]= e[s] + k[s] - __builtin_popcount(s); return ret; } template // O(V 2^V) inline sps edge_space_size() const { return space_size(edge_space_rank()); } template // O(V 2^V) inline sps loop_ignored_edge_space_size() const { return space_size(loop_ignored_edge_space_rank()); } template // O(V 2^V) inline sps cycle_space_size() const { return space_size(cycle_space_rank()); } template // O(V^2 2^V) inline sps connected_graph_num() const { return SPS::log(edge_space_size()); } template // O(V^2 2^V) inline sps loop_ignored_connected_graph_num() const { return SPS::log(loop_ignored_edge_space_size()); } template // O(V^2 2^V) inline sps euler_graph_num() const { return SPS::log(cycle_space_size()); } template // O(V^2 2^V) inline sps connected_biparate_graph_num() const { sps tmp= edge_space_size(), ret(sz, 1); for (int s= sz; s--;) ret[s]/= tmp[s]; ret= SPS::convolve(ret, ret); for (int s= sz; s--;) ret[s]*= tmp[s]; ret= SPS::log(ret); for (int s= sz; s--;) ret[s]/= 2; return ret; } template // O(V^3 2^V) inline sps loop_ignored_biconnected_graph_num() const { auto ret= loop_ignored_connected_graph_num(); return connect_to_biconnect(ret), ret; } template // O(V^3 2^V) inline sps biconnected_graph_num() const { auto ret= loop_ignored_biconnected_graph_num(); return loop_ignored_to_loop_permitted(ret), ret; } template // O(V^2 2^V) inline sps spanning_tree_num() const { sps e= edge_space_rank(); sps ret= {0, 1}; ret.reserve(sz); for (int I= 2; I < sz; I<<= 1) { sps g(ret); for (int s= I; --s;) g[s]*= e[s | I] - e[s] - e[I]; g= SPS::exp(g); std::copy(g.begin(), g.end(), std::back_inserter(ret)); } return ret; } template // O(V^2 2^V) inline sps forest_num() const { return SPS::exp(spanning_tree_num()); } template // O(V^2 2^V) inline sps rooted_spanning_tree_num() const { auto ret= spanning_tree_num(); for (int s= sz; s--;) ret[s]*= __builtin_popcount(s); return ret; } template // O(V^2 2^V) inline sps rooted_forest_num() const { return SPS::exp(rooted_spanning_tree_num()); } template // O(V^2 2^V) inline sps cycle_graph_num() const { T dp[sz][V - 1]; sps ret(sz, 0); for (int i= V, I= sz; I>>= 1, --i;) { for (int s= I; --s;) std::fill_n(dp[s], i, 0); for (int j= i; j--;) dp[1 << j][j]= adj[i][j]; for (int s= 1; s < I; s++) for (int t= s, j, u, r, k; t; ret[s | I]+= dp[s][j] * adj[j][i]) for (t^= 1 << (j= __builtin_ctz(t)), u= r= s ^ (1 << j); u; dp[s][j]+= dp[r][k] * adj[k][j]) u^= 1 << (k= __builtin_ctz(u)); } for (int i= V; i--;) for (int j= i; j--;) ret[(1 << i) | (1 << j)]-= adj[i][j]; for (int s= sz; --s;) ret[s]/= 2; for (int i= V; i--;) ret[1 << i]= adj[i][i]; return ret; } template // O(V^3 2^V) inline sps cactus_graph_num() const { auto ret= cycle_graph_num(); for (int i= V; i--;) for (int j= i; j--;) ret[(1 << i) | (1 << j)]+= adj[i][j]; for (int i= V; i--;) ret[1 << i]= 0; return biconnect_to_connect(ret), loop_ignored_to_loop_permitted(ret), ret; } template // O(V^3 2^V) inline sps two_edge_connected_graph_num() const { const int sz2= sz / 2; sps ret= connected_graph_num(), tmp(sz2), tmp2; for (int i= V, I= sz2; --i; I>>= 1) { for (int t= 0; t < sz2; t+= I) for (int u= I, t2= t << 1; u--;) tmp[t | u]= ret[t2 | I | u]; tmp2.assign(sz2, 0); for (int t= 0; t < sz2; t+= I) for (int j= i, J= I, t2= t << 1; J>>= 1, j--;) for (int s= J, J2= J * 2; s < I; s+= J2) for (int u= s + J; u-- > s;) tmp2[t | u]-= ret[t2 | u] * adj[i][j]; tmp= SPS::convolve(tmp, SPS::exp(tmp2)); for (int t= 0; t < sz2; t+= I) for (int u= I, t2= t << 1; u--;) ret[t2 | I | u]= tmp[t | u]; } return ret; } template // O(V^3 2^V) inline void transform_connect_by_bridge(sps &f) const { const int sz2= sz / 2; sps tmp(sz2), tmp2; for (int i= V, I= sz2; --i; I>>= 1) { for (int t= 0; t < sz2; t+= I) for (int u= I, t2= t << 1; u--;) tmp[t | u]= f[t2 | I | u]; tmp2.assign(sz2, 0); for (int t= 0; t < sz2; t+= I) for (int j= i, J= I, t2= t << 1; J>>= 1, j--;) for (int s= J, J2= J * 2; s < I; s+= J2) for (int u= s + J; u-- > s;) tmp2[t | u]+= f[t2 | u] * adj[i][j]; tmp= SPS::convolve(tmp, SPS::exp(tmp2)); for (int t= 0; t < sz2; t+= I) for (int u= I, t2= t << 1; u--;) f[t2 | I | u]= tmp[t | u]; } } template // O(V^2 2^V) inline sps acyclic_orientations() const { auto k= connected_component_num(); sps g(sz, 0); for (int s= sz; --s;) if (k[s] == __builtin_popcount(s)) g[s]= (k[s] + 1) & 1 ? -1 : 1; return SPS::template semi_relaxed_convolve(g, 1); } template // O(V^2 2^V) inline std::vector colorings_using_exactly_k_colors_num() const { if (V == 0) return {0}; // impossible in any number of ways for (int i= V; i--;) if (adj[i][i]) return {0}; // impossible in any number of ways auto k= connected_component_num(); std::vector indep(sz, 0); for (int s= sz; --s;) indep[s]= k[s] == __builtin_popcount(s); return SPS::egf(indep); } template // O(V^2 2^V) inline poly chromatic_polynomial() const { auto e= colorings_using_exactly_k_colors_num(); if (e.back() == 0) return {0}; poly ret(V + 1, 0); T tmp[V]= {1}; for (int i= 1, j; i < V; i++) for (j= i; j--; tmp[j]*= -i) ret[j + 1]+= tmp[j] * e[i], tmp[j + 1]+= tmp[j]; for (int j= V; j--;) ret[j + 1]+= tmp[j]; return ret; } template // O(V^2 2^V) inline T tutte_polynomial(T x, T y) const { int sum[sz], s, t, lim= 2, i, j; T fum[10'000]= {0, 1}; std::vector g= {0}, h; for (g.reserve(sz), h.reserve(sz), i= 0; i < V; i++) { for (sum[0]= j= 0; j < i; j++) for (s= t= 1 << j; s--;) sum[s | t]= sum[s] + adj[i][j]; for (h.resize(s= 1 << i); s--; h[s]= g[s] * fum[sum[s]]) for (; lim <= sum[s]; lim++) fum[lim]= fum[lim - 1] * y + 1; h= SPS::exp(h), std::copy(h.begin(), h.end(), std::back_inserter(g)); } for (x-= 1, t= ~0, j= 0, i= V; i--;) j+= adj[i][i]; for (bfs((s= sz) - 1, [&](int u) { t^= u; }); --s&= t;) g[s]*= x; return SPS::exp(g)[sz - 1] * pow(y, j); } }; using namespace std; signed main() { cin.tie(0); ios::sync_with_stdio(0); using GSPS= UndirectedGraphSetPowerSeries<15>; using SPS= SetPowerSeries<15>; int n, m; cin >> n >> m; GSPS g(n); for (int i= 0; i < m; ++i) { int u, v; cin >> u >> v, --u, --v; g.add_edge(u, v); } auto f= g.cycle_graph_num(); for (int i= n; i--;) f[1 << i]= 1; g.transform_connect_by_bridge(f); f= SetPowerSeries<>::exp(f); cout << f.back() << '\n'; return 0; }