#include #include #include #include #include namespace library { namespace bits { template , std::nullptr_t> = nullptr> size_t bit_width(const T v) { size_t res = 0; while (T{1} << res <= v) ++res; return res; } template , std::nullptr_t> = nullptr> T bit_deposite(const T src, const T mask, const size_t bitwidth) { T dst = 0; size_t j = 0; for (size_t i = 0; i < bitwidth; ++i) { if ((mask >> i) & 1) dst |= ((src >> j) & 1) << i, ++j; } return dst; } template , std::nullptr_t> = nullptr> size_t popcount(const T v) { if constexpr (std::numeric_limits>::digits <= 32) { return __builtin_popcount(v); } else { return __builtin_popcountll(v); } } template , std::nullptr_t> = nullptr> size_t count_tz(const T v) { if constexpr (std::numeric_limits>::digits <= 32) { return __builtin_ctz(v); } else { return __builtin_ctzll(v); } } } namespace kronecker_power_transform { namespace details { template void unit_transform(RefGettter ref, std::index_sequence) { transform(ref(I)...); } } template void kronecker_power_transform(std::vector &x) { const size_t n = x.size(); for (size_t block = 1; block < n; block *= D) { for (size_t l = 0; l < n; l += D * block) { for (size_t offset = l; offset < l + block; ++offset) { const auto ref = [&](size_t i) -> T& { return x[offset + i * block]; }; details::unit_transform(ref, std::make_index_sequence{}); } } } } } namespace subset_transform { namespace details { template void zeta_unit(T &x0, T &x1) { x1 += x0; } template void mobius_unit(T &x0, T &x1) { x1 -= x0; } } template void zeta(std::vector &a) { kronecker_power_transform::kronecker_power_transform>(a); } template void mobius(std::vector &a) { kronecker_power_transform::kronecker_power_transform>(a); } } namespace set_power_series { namespace details { template struct polynomial : std::vector { using std::vector::vector; polynomial& operator+=(const polynomial &q) { assert(this->size() == q.size()); for (size_t i = 0; i < q.size(); ++i) (*this)[i] += q[i]; return *this; } polynomial& operator-=(const polynomial &q) { assert(this->size() == q.size()); for (size_t i = 0; i < q.size(); ++i) (*this)[i] -= q[i]; return *this; } polynomial& operator*=(const polynomial &q) { const size_t n = this->size(); assert(n == q.size()); polynomial r(n); for (size_t i = 0; i < n; ++i) for (size_t j = 0; i + j < n; ++j) r[i + j] += (*this)[i] * q[j]; return *this = std::move(r); } polynomial operator+(const polynomial &q) { polynomial p = *this; p += q; return p; } polynomial operator-(const polynomial &q) { polynomial p = *this; p -= q; return p; } polynomial operator*(const polynomial &q) { polynomial p = *this; p *= q; return p; } }; template std::vector> add_rank(const std::vector& a) { const size_t n = a.size(); std::vector fs(n, polynomial(bits::count_tz(n) + 1, T{0})); for (size_t i = 0; i < n; ++i) fs[i][bits::popcount(i)] = a[i]; return fs; } template std::vector remove_rank(const std::vector>& polys) { const size_t n = polys.size(); std::vector a(n); for (size_t i = 0; i < n; ++i) a[i] = polys[i][bits::popcount(i)]; return a; } } template std::vector subset_convolution(const std::vector& a, const std::vector& b) { const size_t n = a.size(); auto ra = details::add_rank(a); auto rb = details::add_rank(b); subset_transform::zeta(ra); subset_transform::zeta(rb); for (size_t i = 0; i < n; ++i) ra[i] *= rb[i]; subset_transform::mobius(ra); return details::remove_rank(ra); } template std::vector subset_exp(const std::vector& f) { assert(f[0] == 0); const size_t n = bits::bit_width(f.size()) - 1; std::vector g{1}; for (size_t i = 0; i < n; ++i) { std::vector p(f.begin() + (1 << i), f.begin() + (1 << (i + 1))); std::vector h = subset_convolution(std::move(p), g); std::move(h.begin(), h.end(), std::back_inserter(g)); } return g; } } } using vertex = size_t; using vertex_set = size_t; using edge = std::pair; std::vector count_cycles(const size_t n, const size_t, const std::vector &edges) { std::vector adj(n, std::vector{}); for (const auto &[u, v] : edges) adj[u].push_back(v), adj[v].push_back(u); std::vector cycle_dp(1u << n, std::vector(n)); for (size_t v = 0; v < n; ++v) { cycle_dp[1u << v][v] = 1; } std::vector cycle(1u << n); for (vertex_set s = 1; s < 1u << n; ++s) { const vertex start = library::bits::count_tz(s); for (vertex cur = 0; cur < n; ++cur) if (cycle_dp[s][cur]) { for (const vertex nxt : adj[cur]) { if (start == nxt) { cycle[s] += cycle_dp[s][cur]; } else if (start < nxt and not ((s >> nxt) & 1)) { const vertex_set nxt_s = s | (1u << nxt); cycle_dp[nxt_s][nxt] += cycle_dp[s][cur]; } } } } for (vertex_set s = 1; s < 1u << n; ++s) { const size_t popcnt = library::bits::popcount(s); if (popcnt == 1) cycle[s] = 1; else if (popcnt == 2) cycle[s] = 0; else cycle[s] /= 2; } return cycle; } uint64_t solve(const size_t n, const size_t m, const std::vector &edges) { // e[S] := # edges connecting vertices in S. std::vector e(1u << n); for (const auto &[u, v] : edges) { ++e[(1u << u) | (1u << v)]; } library::subset_transform::zeta(e); using sps = std::vector; const sps cycle = count_cycles(n, m, edges); sps f(1u << n); for (vertex_set C = 1; C < 1u << n; ++C) { // max C const vertex t = library::bits::bit_width(C) - 1; // { 0, ..., t } - C const vertex_set S = ((1u << (t + 1)) - 1) ^ C; const size_t k = library::bits::popcount(S); sps g(1u << k); for (vertex_set A = 0; A < 1u << k; ++A) { const vertex_set T = library::bits::bit_deposite(A, S, t); g[A] = f[T] * (e[T | C] - e[T] - e[C]); } const sps h = library::set_power_series::subset_exp(g); for (vertex_set A = 0; A < 1u << k; ++A) { const vertex_set X = library::bits::bit_deposite(A, S, t) | C; f[X] += cycle[C] * h[A]; } } return library::set_power_series::subset_exp(f).back(); } int main() { size_t n, m; std::cin >> n >> m; std::vector edges(m); for (auto &[u, v] : edges) { std::cin >> u >> v; --u, --v; } std::cout << solve(n, m, edges) << '\n'; }