Problem:
D: 分割
問題
\(A\) 市は \(N\) 個の町と \(M\) 本の道を管理している。\(i\) 本目の道は町 \(a_i\) と \(b_i\) を接続している。
\(A\) 市では市の分離が計画されており、\(B\) 市、\(C\) 市、\(D\) 市の \(3\) 市に分離することが決まっている。\(B\) 市の市長になることが決まっている人と \(C\) 市の市長になることが決まっている人の仲が悪いので、\(B\) 市と \(C\) 市の町を直接繋ぐような道を取り壊すことが決まっている。また、利便性の観点からどの町からどの町にも移動できる必要があるが、一方で道の維持費削減のために同じ市に管理される町同士を接続する道の数が減らされることも決まっている。
分離計画は以下の条件を満たす必要がある。
全ての町がいずれかの市のうち \(1\) つに管理される。
全ての市が \(1\) つ以上の町を管理する。
同じ市に管理されている町同士は他の市に管理されている町を経由することなく行き来することができる。
\(B\) 市の町と \(C\) 市の町を接続する道はない。
すべての町のペアについて、行き来することができるような経路が存在する。
以上の条件を満たす計画のなかで、同じ市に管理される町同士を接続する道の数は最小である。
これらの条件を満たす計画としてあり得るものの数を \(998244353\) で割ったあまりを求めよ。
入力形式
N
M
a_1
b_1
a_2
b_2
︙
a_M
b_M
制約
\(2\leq N\leq 17\)
\(1\leq M\,\leq \, \frac{N(N-1)}{2}\)
\(1\leq a_i\leq b_i\leq N\)
\(i\neq j\) ならば \(a_i \neq a_j\) か \(b_i \neq b_j\)
入力はすべて整数
出力形式
条件を満たす計画としてあり得るものの数を \(998244353\) で割ったあまりを出力せよ。
入力例1
4 4
1 2
1 3
1 4
2 4
出力例1
24
たとえば、町 \(1\) と \(2\) を \(D\) 市が、町 \(3\) を \(B\) 市が、町 \(4\) を \(C\) 市が管理し、全ての辺を残す場合があります。
入力例2
3 2
1 2
2 3
出力例2
2
町 \(1\) を \(B\) 市、町 \(2\) を \(D\) 市、町 \(3\) を \(C\) 市が管理し全ての道を残す場合と町 \(1\) を \(C\) 市、町 \(2\) を \(D\) 市、町 \(3\) を \(B\) 市が管理し全ての道を残す場合があります。

Submitted solutions:

code_snippet: #include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <tuple>
#include <vector>

#define mkp make_pair
#define mkt make_tuple
#define rep(i, n) for (int i = 0; i < (int)(n); ++i)
#define all(v) v.begin(), v.end()
using namespace std;
typedef long long ll;
template <class T>
void chmin(T &a, const T &b) {
    if (a > b) a = b;
}
template <class T>
void chmax(T &a, const T &b) {
    if (a < b) a = b;
}

template <long long mod>
class modint {
private:
    using T = long long;
    T a;

public:
    constexpr modint(const long long x = 0) noexcept : a((x % mod + mod) % mod) {}
    constexpr T &value() noexcept {
        return a;
    }
    constexpr const T &value() const noexcept {
        return a;
    }
    constexpr modint operator-() const noexcept {
        return modint(0) -= *this;
    }
    constexpr modint operator+(const modint &rhs) const noexcept {
        return modint(*this) += rhs;
    }
    constexpr modint operator-(const modint &rhs) const noexcept {
        return modint(*this) -= rhs;
    }
    constexpr modint operator*(const modint &rhs) const noexcept {
        return modint(*this) *= rhs;
    }
    constexpr modint operator/(const modint &rhs) const noexcept {
        return modint(*this) /= rhs;
    }
    constexpr modint &operator+=(const modint &rhs) noexcept {
        a += rhs.a;
        if (a >= mod) a -= mod;
        return *this;
    }
    constexpr modint &operator-=(const modint &rhs) noexcept {
        if (a < rhs.a) a += mod;
        a -= rhs.a;
        return *this;
    }
    constexpr modint &operator*=(const modint &rhs) noexcept {
        a = a * rhs.a % mod;
        return *this;
    }
    constexpr modint &operator/=(const modint &rhs) noexcept {
        return *this *= rhs.inv();
    }
    constexpr bool operator==(const modint &rhs) const noexcept {
        return a == rhs.a;
    }
    constexpr bool operator!=(const modint &rhs) const noexcept {
        return not(*this == rhs);
    }
    constexpr modint pow(long long k) const noexcept {
        modint ret(1);
        modint x = k > 0 ? *this : this->inv();
        k = abs(k);
        while (k > 0) {
            if (k & 1) ret *= x;
            x *= x;
            k >>= 1;
        }
        return ret;
    }
    constexpr modint inv() const noexcept {
        return pow(mod - 2);
    }
    friend std::ostream &operator<<(std::ostream &os, const modint &X) noexcept {
        return os << X.a;
    }
    friend std::istream &operator>>(std::istream &is, modint &X) noexcept {
        is >> X.a;
        X.a %= mod;
        if (X.a < 0) X.a += mod;
        return is;
    }
};

/*
    calculate the determinant of a square matrix A
    keyword : matrix-tree theorem
    time complexity : O(N^3)
*/

template <typename T>
T calc_determinant(vector<vector<T>> A) {
    T ans = 1;
    int N = A.size();

    for (int j = 0; j < N; j++) {
        int pivot = -1;
        for (int i = j; i < N; i++) {
            if (A[i][j] != 0) {
                pivot = i;
                break;
            }
        }
        if (pivot == -1) return T(0);

        if (pivot != j) {
            swap(A[j], A[pivot]);
            ans *= T(-1);
        }

        ans *= A[j][j];
        T inv = T(1) / A[j][j];
        for (int i = 0; i < N; i++) A[j][i] *= inv;

        for (int i = j + 1; i < N; i++) {
            T coef = A[i][j];
            for (int k = 0; k < N; k++) A[i][k] -= A[j][k] * coef;
        }
    }
    return ans;
}

const ll MOD = 998244353;
using mint = modint<MOD>;

mint calc_num_spanning_tree(vector<vector<mint>> M) {
    int V = M.size();
    vector<vector<mint>> B(V - 1, vector<mint>(V - 1, 0));
    rep(i, V) rep(j, V) {
        if (i + 1 < V && j + 1 < V) B[i][j] = M[i][j];
    }

    return calc_determinant(B);
}

void solve() {
    int N, M;
    cin >> N >> M;
    vector<vector<int>> has_edge(N, vector<int>(N, 0));
    rep(i, M) {
        int a, b;
        cin >> a >> b;
        a--;
        b--;
        has_edge[a][b] = 1;
        has_edge[b][a] = 1;
    }

    vector<mint> num_spanning_tree(1 << N, 0);
    for (int bit = 0; bit < (1 << N); bit++) {
        int V = __builtin_popcount(bit);
        if (V <= 1) {
            num_spanning_tree[bit] = 1;
            continue;
        }

        vector<int> nodes;
        rep(i, N) if (bit >> i & 1) nodes.push_back(i);

        vector<vector<mint>> M(V, vector<mint>(V, 0));
        rep(i, V) {
            rep(j, V) if (has_edge[nodes[i]][nodes[j]]) M[i][i] += 1;
            rep(j, V) if (has_edge[nodes[i]][nodes[j]]) M[i][j] = mint(-1);
        }
        num_spanning_tree[bit] = calc_num_spanning_tree(M);
    }

    vector<int> num_inner_roads(1 << N, 0);
    for (int bit = 0; bit < (1 << N); bit++) {
        int V = __builtin_popcount(bit);
        vector<int> nodes;
        rep(i, N) if (bit >> i & 1) nodes.push_back(i);

        int res = 0;
        rep(i, V) {
            rep(j, i) if (has_edge[nodes[i]][nodes[j]]) res++;
        }
        num_inner_roads[bit] = res;
    }

    vector<mint> two(M + 1, 0);
    two[0] = 1;
    rep(i, M) two[i + 1] = two[i] * 2;

    mint ans = 0;
    for (int bit = 0; bit < (1 << N); bit++) {
        if (bit == 0) continue;

        vector<int> num_con(N, 0);
        rep(i, N) if (!(bit >> i & 1)) {
            rep(j, N) if ((bit >> j & 1) && has_edge[i][j]) num_con[i]++;
        }

        int bc_bit = ((1 << N) - 1) ^ bit;
        for (int s = bc_bit; s > 0; s = (s - 1) & bc_bit) {
            if (s == bc_bit) continue;

            int t = bc_bit ^ s;

            int s_num = num_inner_roads[s | bit] - num_inner_roads[s] - num_inner_roads[bit];
            int t_num = num_inner_roads[t | bit] - num_inner_roads[t] - num_inner_roads[bit];
            if (s_num && t_num) {
                mint res = num_spanning_tree[bit] * num_spanning_tree[s] * num_spanning_tree[t];
                res *= two[s_num] - 1;
                res *= two[t_num] - 1;
                ans += res;
            }
        }
    }

    cout << ans << endl;
}

int main() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);

    solve();

    return 0;
}
accuracy: 1
time_ms: 1200
memory_kb: 4668
final_rank: 5

code_snippet: #include <bits/stdc++.h>
#include <variant>

#define rep2(i,k,n) for(i64 i=(i64)(k);i<(i64)(n);i++)
#define rep(i,n) rep2(i,0,n)
#define all(x) begin(x),end(x)
#ifdef ENV_LOCAL
#define dump if (1) cerr
#else
#define dump if (0) cerr
#endif

using namespace std;
using namespace std::string_literals;

using i32 = int32_t;
using i64 = int64_t;
using f64 = double;
using f80 = long double;
using vi32 = vector<i32>;
using vi64 = vector<i64>;
/*
 * harudake::modint
 *
 * Copyright (c) 2021 prime number
 *
 * This software is released under the MIT license.
 * see https://opensource.org/licenses/MIT
 *
 */
namespace harudake {

template <typename base_t, base_t MOD>
class static_modint;

using modint_1000000007 = static_modint<uint64_t, 1'000'000'007>;
using modint_998244353 = static_modint<uint64_t, 998'244'353>;

template <typename base_t, base_t MOD>
static_modint<base_t, MOD> add(const static_modint<base_t, MOD> lhs, const static_modint<base_t, MOD> rhs) {
  base_t tmp = lhs.val + rhs.val;
  if (tmp >= MOD) tmp -= MOD;
  return static_modint<base_t, MOD>::make_raw(tmp);
}

template <typename base_t, base_t MOD>
static_modint<base_t, MOD> sub(const static_modint<base_t, MOD> lhs, const static_modint<base_t, MOD> rhs) {
  base_t tmp = lhs.val + MOD - rhs.val;
  if (tmp >= MOD) tmp -= MOD;
  return static_modint<base_t, MOD>::make_raw(tmp);
}

template <typename base_t, base_t MOD>
static_modint<base_t, MOD> mul(const static_modint<base_t, MOD> lhs, const static_modint<base_t, MOD> rhs) {
  base_t tmp = lhs.val * rhs.val;
  tmp %= MOD;
  return static_modint<base_t, MOD>::make_raw(tmp);
}

// data types must be signed integer
inline int64_t inv (const int64_t a, const int64_t p) {
  return (a == 1 ? 1 : (1 - p * inv(p%a, a)) / a + p);
};

template <typename base_t, base_t MOD>
static_modint<base_t, MOD> div(const static_modint<base_t, MOD> lhs, const static_modint<base_t, MOD> rhs) {
  base_t tmp = lhs.val * inv(rhs.val, MOD);
  tmp %= MOD;
  return static_modint<base_t, MOD>::make_raw(tmp);
}

template <typename base_t, base_t MOD>
static_modint<base_t, MOD> pow(const static_modint<base_t, MOD> base, const uint64_t index) {
  if (index == 0) return static_modint<base_t, MOD> { 1 };
  if (index % 2 == 1) {
    return pow(base, index - 1) * base;
  } else {
    auto half = pow(base, index / 2);
    return half * half;
  }
}

template <typename base_t, base_t MOD>
class static_modint {
  using modint = static_modint;
 public:
  static constexpr base_t mod = MOD;
  static_modint() : val(0) {}
  static_modint(const base_t x) : val(x % MOD) {}
  static_modint(const modint&) = default;
  modint& operator=(const modint&) = default;
  modint& operator=(const base_t x) {
    val = x % MOD;
    return *this;
  }
  modint operator-() const { return val == 0 ? 0 : MOD - val; }
  explicit operator base_t() const { return val; }
  base_t get() const { return val; }
  constexpr base_t get_mod() const { return mod; }
  static modint make_raw(const base_t raw) {
    modint res;
    res.val = raw;
    return res;
  }
 private:
  friend modint add<base_t, MOD>(const modint, const modint);
  friend modint sub<base_t, MOD>(const modint, const modint);
  friend modint mul<base_t, MOD>(const modint, const modint);
  friend modint div<base_t, MOD>(const modint, const modint);
  base_t val;
};

template <typename base_t, base_t MOD>
static_modint<base_t, MOD> operator+(const static_modint<base_t, MOD> lhs, const static_modint<base_t, MOD> rhs) {
  return add(lhs, rhs);
}
template <typename base_t, base_t MOD>
static_modint<base_t, MOD> operator-(const static_modint<base_t, MOD> lhs, const static_modint<base_t, MOD> rhs) {
  return sub(lhs, rhs);
}
template <typename base_t, base_t MOD>
static_modint<base_t, MOD> operator*(const static_modint<base_t, MOD> lhs, const static_modint<base_t, MOD> rhs) {
  return mul(lhs, rhs);
}
template <typename base_t, base_t MOD>
static_modint<base_t, MOD> operator/(const static_modint<base_t, MOD> lhs, const static_modint<base_t, MOD> rhs) {
  return div(lhs, rhs);
}
template <typename base_t, base_t MOD>
static_modint<base_t, MOD> operator^(const static_modint<base_t, MOD> lhs, const uint64_t rhs) {
  return pow(lhs, rhs);
}

template <typename base_t, base_t MOD>
static_modint<base_t, MOD>& operator+=(static_modint<base_t, MOD>& lhs, const static_modint<base_t, MOD> rhs) {
  return lhs = lhs + rhs;
}
template <typename base_t, base_t MOD>
static_modint<base_t, MOD>& operator-=(static_modint<base_t, MOD>& lhs, const static_modint<base_t, MOD> rhs) {
  return lhs = lhs - rhs;
}
template <typename base_t, base_t MOD>
static_modint<base_t, MOD>& operator*=(static_modint<base_t, MOD>& lhs, const static_modint<base_t, MOD> rhs) {
  return lhs = lhs * rhs;
}
template <typename base_t, base_t MOD>
static_modint<base_t, MOD>& operator/=(static_modint<base_t, MOD>& lhs, const static_modint<base_t, MOD> rhs) {
  return lhs = lhs / rhs;
}
template <typename base_t, base_t MOD>
static_modint<base_t, MOD>& operator^=(static_modint<base_t, MOD>& lhs, const uint64_t rhs) {
  return lhs = lhs ^ rhs;
}

} // namespace harudake

using namespace harudake;
using modint = modint_998244353;

using mat = vector<vector<modint>>;

modint det(mat m) {
  i32 n = size(m);
  modint ans(1);
  rep(i,n) {
    rep2(j,i+1,n) {
      if (m[j][i].get() != 0) {
        swap(m[i], m[j]);
        ans *= modint(998244353-1);
        break;
      }
    }
    if (m[i][i].get() == 0) {
      return 0;
    }
    ans *= m[i][i];
    rep2(j,i+1,n) {
      auto coeff = m[j][i] / m[i][i];
      rep2(k,i,n) {
        m[j][k] -= coeff * m[i][k];
      }
    }
  }
  return ans;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  i64 n,m;
  cin>>n>>m;
  mat g(n, vector<modint>(n, 0));
  rep(i,m) {
    i32 a,b;
    cin>>a>>b;
    --a;--b;
    g[a][b] = 1;
    g[b][a] = 1;
  }
  modint ans = 0;
  vector<modint> cache(1<<n);
  vi32 num_roads(1<<n);
  rep(i,1<<n) {
    vi32 v;
    rep(j,n) {
      if ((i>>j)&1) {
        v.push_back(j);
      }
    }
    if (size(v) < 1) continue;
    i32 k = size(v);
    mat sub(k, vector<modint>(k));
    i32 cnt = 0;
    rep(r,k)rep(c,k) {
      if (g[v[r]][v[c]].get() == 1) {
        sub[r][c] = 998244353-1;
        ++cnt;
      }
    }
    num_roads[i] = cnt/2; // bidirectional counted
    rep(r,k) {
      modint sum = 0;
      rep(c,k) {
        sum += sub[r][c];
      }
      sub[r][r] = -sum;
      sub[r].pop_back();
    }
    sub.pop_back();
    cache[i] = det(sub);
    dump<<num_roads[i]<<" "<<cache[i].get()<<endl;
  }
  vector<modint> num_cache(m+1);
  rep(i,m+1) {
    num_cache[i] = (modint(2) ^ i) - modint(1);
  }
  rep(i,1<<n) {
    for (i32 U = (1 << n) - 1; U >= 0; --U) {
      U &= ~i;
      i32 V = (((1 << n) - 1) & ~i) ^ U;
      auto bd = num_roads[i|U] - num_roads[i] - num_roads[U];
      auto cd = num_roads[i|V] - num_roads[i] - num_roads[V];
      ans += cache[i] * cache[U] * cache[V] * num_cache[bd] * num_cache[cd];
    }
  }
  cout<<ans.get()<<endl;
  
  return 0;
}
accuracy: 1
time_ms: 1070
memory_kb: 4660
final_rank: 3

code_snippet: #define _USE_MATH_DEFINES
#include <bits/stdc++.h>
using namespace std;
#define FOR(i,m,n) for(int i=(m);i<(n);++i)
#define REP(i,n) FOR(i,0,n)
#define ALL(v) (v).begin(),(v).end()
using ll = long long;
constexpr int INF = 0x3f3f3f3f;
constexpr long long LINF = 0x3f3f3f3f3f3f3f3fLL;
constexpr double EPS = 1e-8;
constexpr int MOD = 998244353;
constexpr int DY[]{1, 0, -1, 0}, DX[]{0, -1, 0, 1};
constexpr int DY8[]{1, 1, 0, -1, -1, -1, 0, 1}, DX8[]{0, -1, -1, -1, 0, 1, 1, 1};
template <typename T, typename U> inline bool chmax(T &a, U b) { return a < b ? (a = b, true) : false; }
template <typename T, typename U> inline bool chmin(T &a, U b) { return a > b ? (a = b, true) : false; }
struct IOSetup {
  IOSetup() {
    std::cin.tie(nullptr);
    std::ios_base::sync_with_stdio(false);
    std::cout << fixed << setprecision(20);
  }
} iosetup;

template <int M>
struct MInt {
  unsigned int val;
  MInt(): val(0) {}
  MInt(long long x) : val(x >= 0 ? x % M : x % M + M) {}
  static constexpr int get_mod() { return M; }
  static void set_mod(int divisor) { assert(divisor == M); }
  static void init(int x = 10000000) { inv(x, true); fact(x); fact_inv(x); }
  static MInt inv(int x, bool init = false) {
    // assert(0 <= x && x < M && std::__gcd(x, M) == 1);
    static std::vector<MInt> inverse{0, 1};
    int prev = inverse.size();
    if (init && x >= prev) {
      // "x!" and "M" must be disjoint.
      inverse.resize(x + 1);
      for (int i = prev; i <= x; ++i) inverse[i] = -inverse[M % i] * (M / i);
    }
    if (x < inverse.size()) return inverse[x];
    unsigned int a = x, b = M; int u = 1, v = 0;
    while (b) {
      unsigned int q = a / b;
      std::swap(a -= q * b, b);
      std::swap(u -= q * v, v);
    }
    return u;
  }
  static MInt fact(int x) {
    static std::vector<MInt> f{1};
    int prev = f.size();
    if (x >= prev) {
      f.resize(x + 1);
      for (int i = prev; i <= x; ++i) f[i] = f[i - 1] * i;
    }
    return f[x];
  }
  static MInt fact_inv(int x) {
    static std::vector<MInt> finv{1};
    int prev = finv.size();
    if (x >= prev) {
      finv.resize(x + 1);
      finv[x] = inv(fact(x).val);
      for (int i = x; i > prev; --i) finv[i - 1] = finv[i] * i;
    }
    return finv[x];
  }
  static MInt nCk(int n, int k) {
    if (n < 0 || n < k || k < 0) return 0;
    if (n - k > k) k = n - k;
    return fact(n) * fact_inv(k) * fact_inv(n - k);
  }
  static MInt nPk(int n, int k) { return n < 0 || n < k || k < 0 ? 0 : fact(n) * fact_inv(n - k); }
  static MInt nHk(int n, int k) { return n < 0 || k < 0 ? 0 : (k == 0 ? 1 : nCk(n + k - 1, k)); }
  static MInt large_nCk(long long n, int k) {
    if (n < 0 || n < k || k < 0) return 0;
    inv(k, true);
    MInt res = 1;
    for (int i = 1; i <= k; ++i) res *= inv(i) * n--;
    return res;
  }
  MInt pow(long long exponent) const {
    MInt tmp = *this, res = 1;
    while (exponent > 0) {
      if (exponent & 1) res *= tmp;
      tmp *= tmp;
      exponent >>= 1;
    }
    return res;
  }
  MInt &operator+=(const MInt &x) { if((val += x.val) >= M) val -= M; return *this; }
  MInt &operator-=(const MInt &x) { if((val += M - x.val) >= M) val -= M; return *this; }
  MInt &operator*=(const MInt &x) { val = static_cast<unsigned long long>(val) * x.val % M; return *this; }
  MInt &operator/=(const MInt &x) { return *this *= inv(x.val); }
  bool operator==(const MInt &x) const { return val == x.val; }
  bool operator!=(const MInt &x) const { return val != x.val; }
  bool operator<(const MInt &x) const { return val < x.val; }
  bool operator<=(const MInt &x) const { return val <= x.val; }
  bool operator>(const MInt &x) const { return val > x.val; }
  bool operator>=(const MInt &x) const { return val >= x.val; }
  MInt &operator++() { if (++val == M) val = 0; return *this; }
  MInt operator++(int) { MInt res = *this; ++*this; return res; }
  MInt &operator--() { val = (val == 0 ? M : val) - 1; return *this; }
  MInt operator--(int) { MInt res = *this; --*this; return res; }
  MInt operator+() const { return *this; }
  MInt operator-() const { return MInt(val ? M - val : 0); }
  MInt operator+(const MInt &x) const { return MInt(*this) += x; }
  MInt operator-(const MInt &x) const { return MInt(*this) -= x; }
  MInt operator*(const MInt &x) const { return MInt(*this) *= x; }
  MInt operator/(const MInt &x) const { return MInt(*this) /= x; }
  friend std::ostream &operator<<(std::ostream &os, const MInt &x) { return os << x.val; }
  friend std::istream &operator>>(std::istream &is, MInt &x) { long long val; is >> val; x = MInt(val); return is; }
};
namespace std { template <int M> MInt<M> abs(const MInt<M> &x) { return x; } }
using ModInt = MInt<MOD>;

template <typename Ring>
std::vector<Ring> fast_zeta_transform(
  std::vector<Ring> a,
  bool adds_superset,
  const Ring ID = 0,
  const std::function<Ring(Ring, Ring)> fn = [](const Ring &a, const Ring &b) -> Ring { return a + b; }
) {
  int n = a.size(), p = 1;
  while ((1 << p) < n) ++p;
  n = 1 << p;
  a.resize(n, ID);
  if (adds_superset) {
    for (int i = 1; i < n; i <<= 1) for (int s = 0; s < n; ++s) {
      if (s & i) continue;
      a[s] = fn(a[s], a[s | i]);
    }
  } else {
    for (int i = 1; i < n; i <<= 1) for (int s = 0; s < n; ++s) {
      if (s & i) continue;
      a[s | i] = fn(a[s | i], a[s]);
    }
  }
  return a;
}

template <typename T>
struct Matrix {
  Matrix(int m, int n, T val = 0) : dat(m, std::vector<T>(n, val)) {}

  int height() const { return dat.size(); }

  int width() const { return dat.front().size(); }

  Matrix pow(long long exponent) const {
    int n = height();
    Matrix<T> tmp = *this, res(n, n, 0);
    for (int i = 0; i < n; ++i) res[i][i] = 1;
    while (exponent > 0) {
      if (exponent & 1) res *= tmp;
      tmp *= tmp;
      exponent >>= 1;
    }
    return res;
  }

  inline const std::vector<T> &operator[](const int idx) const { return dat[idx]; }
  inline std::vector<T> &operator[](const int idx) { return dat[idx]; }

  Matrix &operator=(const Matrix &x) {
    int m = x.height(), n = x.width();
    dat.resize(m, std::vector<T>(n));
    for (int i = 0; i < m; ++i) for (int j = 0; j < n; ++j) dat[i][j] = x[i][j];
    return *this;
  }

  Matrix &operator+=(const Matrix &x) {
    int m = height(), n = width();
    for (int i = 0; i < m; ++i) for (int j = 0; j < n; ++j) dat[i][j] += x[i][j];
    return *this;
  }

  Matrix &operator-=(const Matrix &x) {
    int m = height(), n = width();
    for (int i = 0; i < m; ++i) for (int j = 0; j < n; ++j) dat[i][j] -= x[i][j];
    return *this;
  }

  Matrix &operator*=(const Matrix &x) {
    int m = height(), n = x.width(), l = width();
    std::vector<std::vector<T>> res(m, std::vector<T>(n, 0));
    for (int i = 0; i < m; ++i) for (int j = 0; j < n; ++j) {
      for (int k = 0; k < l; ++k) res[i][j] += dat[i][k] * x[k][j];
    }
    std::swap(dat, res);
    return *this;
  }

  Matrix operator+(const Matrix &x) const { return Matrix(*this) += x; }

  Matrix operator-(const Matrix &x) const { return Matrix(*this) -= x; }

  Matrix operator*(const Matrix &x) const { return Matrix(*this) *= x; }

private:
  std::vector<std::vector<T>> dat;
};

template <typename T, typename U>
U det(const Matrix<T> &mat, const U EPS) {
  int n = mat.height();
  Matrix<U> tmp(n, n);
  for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) tmp[i][j] = mat[i][j];
  U res = 1;
  for (int j = 0; j < n; ++j) {
    int pivot = -1;
    U mx = EPS;
    for (int i = j; i < n; ++i) {
      if (std::abs(tmp[i][j]) > mx) {
        pivot = i;
        mx = std::abs(tmp[i][j]);
      }
    }
    if (pivot == -1) return 0;
    if (pivot != j) {
      std::swap(tmp[j], tmp[pivot]);
      res = -res;
    }
    res *= tmp[j][j];
    for (int k = j + 1; k < n; ++k) tmp[j][k] /= tmp[j][j];
    for (int i = j + 1; i < n; ++i) for (int k = j + 1; k < n; ++k) {
      tmp[i][k] -= tmp[i][j] * tmp[j][k];
    }
  }
  return res;
}

template <typename T>
T matrix_tree_theorem(const std::vector<std::vector<int>> &graph, const T EPS) {
  const int n = graph.size();
  Matrix<int> laplacian(n, n, 0);
  for (int i = 0; i < n; ++i) {
    for (const int j : graph[i]) {
      ++laplacian[i][i];
      --laplacian[i][j];
    }
  }
  Matrix<int> cofactor(n - 1, n - 1);
  for (int i = 0; i < n - 1; ++i) {
    for (int j = 0; j < n - 1; ++j) {
      cofactor[i][j] = laplacian[i + 1][j + 1];
    }
  }
  return det(cofactor, EPS);
}

int main() {
  int n, m; cin >> n >> m;
  vector<int> a(m), b(m); REP(i, m) cin >> a[i] >> b[i], --a[i], --b[i];
  vector<int> edge(1 << n, 0);
  REP(i, m) ++edge[(1 << a[i]) | (1 << b[i])];
  edge = fast_zeta_transform(edge, false);
  vector<ModInt> span(1 << n, 1);
  FOR(bit, 1, 1 << n) {
    if (__builtin_popcount(bit) == 1) continue;
    vector<int> node;
    REP(i, n) {
      if (bit >> i & 1) node.emplace_back(i);
    }
    vector<vector<int>> graph(node.size());
    REP(i, m) {
      if ((bit >> a[i] & 1) && (bit >> b[i] & 1)) {
        const int x = lower_bound(ALL(node), a[i]) - node.begin();
        const int y = lower_bound(ALL(node), b[i]) - node.begin();
        graph[x].emplace_back(y);
        graph[y].emplace_back(x);
      }
    }
    span[bit] = matrix_tree_theorem(graph, ModInt(0));
  }
  vector<ModInt> p2(m + 1, 1);
  REP(i, m) p2[i + 1] = p2[i] * 2;
  ModInt ans = 0;
  FOR(d, 1, 1 << n) {
    const int bc = ~d & ((1 << n) - 1);
    for (int c = bc; ;) {
      if (c == 0) break;
      const int B = bc ^ c;
      if (B) {
        const int bd = edge[B | d] - edge[B] - edge[d], cd = edge[c | d] - edge[c] - edge[d];
        ans += span[B] * span[c] * span[d] * (p2[bd] - 1) * (p2[cd] - 1);
      }
      if ((c = (c - 1) & bc) == bc) break;
    }
  }
  cout << ans << '\n';
  return 0;
}
accuracy: 1
time_ms: 1220
memory_kb: 4176
final_rank: 4

code_snippet: #include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <ctime>
#include <cstdlib>
#include <cassert>
#include <vector>
#include <list>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <string>
#include <algorithm>
#include <utility>
#include <complex>
#include <unordered_set>
#include <unordered_map>
#define rep(x, s, t) for(ll x = (s); (x) <= (t); (x)++)
#define per(x, s, t) for(ll x = (s); (x) >= (t); (x)--)
#define reps(x, s) for(ll x = 0; (x) < (ll)(s).size(); (x)++)
#define chmin(x, y) (x) = min((x), (y))
#define chmax(x, y) (x) = max((x), (y))
#define sz(x) ((ll)(x).size())
#define all(x) (x).begin(),(x).end()
#define outl(...) dump_func(__VA_ARGS__)
#define outf(x) cout << fixed << setprecision(16) << (x) << endl
#define inf 2e18
#define eps 1e-9
const double PI = 3.1415926535897932384626433;

using namespace std;

typedef long long llint;
typedef long long ll;
typedef pair<ll, ll> P;

struct edge{
	ll to, cost;
	edge(){}
	edge(ll a, ll b){ to = a, cost = b;}
};
const int dx[] = {1, 0, -1, 0}, dy[] = {0, -1, 0, 1};

//int mod = 1000000007;
int mod = 998244353;

struct mint{
	int x;
	mint(ll y = 0){if(y < 0 || y >= mod) y = (y%mod+mod)%mod; x = y;}
	mint(const mint &ope) {x = ope.x;}
	
	mint operator-(){return mint(-x);}
	mint operator+(const mint &ope){return mint(x) += ope;}
	mint operator-(const mint &ope){return mint(x) -= ope;}
	mint operator*(const mint &ope){return mint(x) *= ope;}
	mint operator/(const mint &ope){return mint(x) /= ope;}
	mint& operator+=(const mint &ope){
		x += ope.x;
		if(x >= mod) x -= mod;
		return *this;
	}
	mint& operator-=(const mint &ope){
		x += mod - ope.x;
		if(x >= mod) x -= mod;
		return *this;
	}
	mint& operator*=(const mint &ope){
		ll tmp = x;
		tmp *= ope.x, tmp %= mod;
		x = tmp;
		return *this;
	}
	mint& operator/=(const mint &ope){
		ll n = mod-2; mint mul = ope;
		while(n){
			if(n & 1) *this *= mul;
			mul *= mul;
			n >>= 1;
		}
		return *this;
	}
	mint inverse(){return mint(1) / *this;}
	bool operator ==(const mint &ope){return x == ope.x;}
	bool operator !=(const mint &ope){return x != ope.x;}
	bool operator <(const mint &ope){return x < ope.x;}
};
mint modpow(mint a, ll n){
	if(n == 0) return mint(1);
	if(n % 2) return a * modpow(a, n-1);
	else return modpow(a*a, n/2);
}
istream& operator >>(istream &is, mint &ope){
	ll t; is >> t, ope.x = t;
	return is;
}
ostream& operator <<(ostream &os, mint &ope){return os << ope.x;}
ostream& operator <<(ostream &os, const mint &ope){return os << ope.x;}

vector<mint> fact, fact_inv;
void make_fact(int n){
	fact.resize(n+1), fact_inv.resize(n+1);
	fact[0] = mint(1); rep(i, 1, n) fact[i] = fact[i-1] * mint(i);
	fact_inv[n] = fact[n].inverse(); per(i, n-1, 0) fact_inv[i] = fact_inv[i+1] * mint(i+1);
}
mint comb(int n, int k){ if(n < 0 || k < 0 || n < k) return mint(0); return fact[n] * fact_inv[k] * fact_inv[n-k];}
mint perm(int n, int k){ return comb(n, k) * fact[k]; }

vector<int> prime, pvec;
void make_prime(int n){
	prime.resize(n+1);
	rep(i, 2, n){
		if(prime[i]) continue;
		for(int j = i; j <= n; j+=i) prime[j] = i;
	}
	rep(i, 2, n) if(prime[i] == i) pvec.push_back(i);
}

bool exceed(ll x, ll y, ll m){return x >= m / y + 1;}
void mark(){ cout << "*" << endl; }
void yes(){ cout << "YES" << endl; }
void no(){ cout << "NO" << endl; }
ll floor(ll a, ll b){ if(b < 0) a *= -1, b *= -1; if(a >= 0) return a/b; else return -((-a+b-1)/b); }
ll ceil(ll a, ll b){ if(b < 0) a *= -1, b *= -1; if(a >= 0) return (a+b-1)/b; else return -((-a)/b); }
ll modulo(ll a, ll b){ b = abs(b); return a - floor(a, b) * b; }
ll sgn(ll x){ if(x > 0) return 1; if(x < 0) return -1; return 0;}
ll gcd(ll a, ll b){if(b == 0) return a; return gcd(b, a%b);}
ll lcm(ll a, ll b){return a/gcd(a, b)*b;}
ll digitnum(ll x, ll b = 10){ll ret = 0; for(; x; x /= b) ret++; return ret;}
ll digitsum(ll x, ll b = 10){ll ret = 0; for(; x; x /= b) ret += x % b; return ret;}
string lltos(ll x){string ret; for(;x;x/=10) ret += x % 10 + '0'; reverse(ret.begin(), ret.end()); return ret;}
ll stoll(string &s){ll ret = 0; for(auto c : s) ret *= 10, ret += c - '0'; return ret;}
template<typename T>
void uniq(T &vec){ sort(vec.begin(), vec.end()); vec.erase(unique(vec.begin(), vec.end()), vec.end());}

template<class S, class T> pair<S, T>& operator+=(pair<S,T> &s, const pair<S,T> &t){
	s.first += t.first, s.second += t.second;
	return s;
}
template<class S, class T> pair<S, T>& operator-=(pair<S,T> &s, const pair<S,T> &t){
	s.first -= t.first, s.second -= t.second;
	return s;
}
template<class S, class T> pair<S, T> operator+(const pair<S,T> &s, const pair<S,T> &t){
	return pair<S,T>(s.first+t.first, s.second+t.second);
}
template<class S, class T> pair<S, T> operator-(const pair<S,T> &s, const pair<S,T> &t){
	return pair<S,T>(s.first-t.first, s.second-t.second);
}
template<typename T>
ostream& operator << (ostream& os, vector<T>& vec) {
	reps(i, vec) os << vec[i] << (i + 1 == vec.size() ? "" : " "); return os;
}
template<typename T>
ostream& operator << (ostream& os, const vector<T>& vec) {
	reps(i, vec) os << vec[i] << (i + 1 == vec.size() ? "" : " "); return os;
}
template<typename T>
ostream& operator << (ostream& os, deque<T>& deq) {
	reps(i,  deq) os << deq[i] << (i + 1 == deq.size() ? "" : " "); return os;
}
template<typename T, typename U>
ostream& operator << (ostream& os, pair<T, U>& pair_var) {
	os << "(" << pair_var.first << ", " << pair_var.second << ")";
	return os;
}
template<typename T, typename U>
ostream& operator << (ostream& os, const pair<T, U>& pair_var) {
	os << "(" << pair_var.first << ", " << pair_var.second << ")";
	return os;
}
template<typename T, typename U>
ostream& operator << (ostream& os, map<T, U>& map_var) {
	for(typename map<T, U>::iterator itr = map_var.begin(); itr != map_var.end(); itr++) {
		os << "(" << itr->first << ", " << itr->second << ")";
		itr++; if(itr != map_var.end()) os << ","; itr--;
	}
	return os;
}
template<typename T>
ostream& operator << (ostream& os, set<T>& set_var) {
	for(typename set<T>::iterator itr = set_var.begin(); itr != set_var.end(); itr++) {
		os << *itr; ++itr; if(itr != set_var.end()) os << " "; itr--;
	}
	return os;
}
template<typename T>
ostream& operator << (ostream& os, multiset<T>& set_var) {
	for(typename multiset<T>::iterator itr = set_var.begin(); itr != set_var.end(); itr++) {
		os << *itr; ++itr; if(itr != set_var.end()) os << " "; itr--;
	}
	return os;
}
template<typename T>
void outa(T a[], ll s, ll t){rep(i, s, t){ cout << a[i]; if(i < t) cout << " ";}cout << endl;}
void dump_func(){cout << endl;}
template <class Head, class... Tail>
void dump_func(Head &&head, Tail &&... tail) {
	cout << head;
	if(sizeof...(Tail) > 0) cout << " ";
	dump_func(std::move(tail)...);
}


template <typename T>
T determinant(vector<vector<T> > mat, ll n)
{
	T ret = T(1);
	for(int i = 0; i < n; i++){
		T max_val = 0; int max_j;
		for(int j = i; j < n; j++){
			if(max_val < mat[j][i]){
				max_val = mat[j][i];
				max_j = j;
			}
		}
		if(max_val == T(0)) return T(0);
		if(i != max_j){
			ret *= T(-1);
			swap(mat[i], mat[max_j]);
		}
		
		T div = mat[i][i].inverse(); ret *= mat[i][i];
		for(int j = 0; j < n; j++) mat[i][j] *= div;
		for(int j = 0; j < n; j++){
			T coe = mat[j][i];
			if(i == j) continue;
			for(int k = 0; k < n; k++) mat[j][k] -= coe * mat[i][k];
		}
	}
	for(int i = 0; i < n; i++) ret *= mat[i][i];
	return ret;
}

template<typename T>
void zeta_transform(T a[], int n){
	rep(i, 0, n-1) rep(j, 0, (1<<n)-1) if((j&(1<<i))) a[j] += a[j^(1<<i)];
}

ll n, m;
ll G[20][20];
int edge[1<<17];
mint mst[1<<17];
mint beki[1005];
vector<vector<mint> > mat;

int main(void)
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	rep(i, 0, 1000) beki[i] = modpow(mint(2), i);
	
	cin >> n >> m;
	ll u, v;
	rep(i, 1, m){
		cin >> u >> v;
		u--, v--;
		if(u == v) continue;
		G[u][v]++, G[v][u]++;
	}
	
	rep(i, 0, n-1) rep(j, 0, n-1) if(i < j) edge[(1<<i)|(1<<j)] += G[i][j];
	zeta_transform(edge, n);
	
	mat.resize(n);
	rep(j, 0, n-1) mat[j].resize(n);
	
	ll N = 1<<n;
	
	
	if(n <= 13){
	rep(i, 1, N-1){
		vector<ll> vec;
		rep(j, 0, n-1) if(i & (1<<j)) vec.push_back(j);
		
		ll m = sz(vec);
		rep(j, 1, m-1){
			rep(k, 1, m-1){
				int x = vec[j], y = vec[k];
				if(x != y) mat[j-1][k-1] = -G[x][y];
				else mat[j-1][k-1] = edge[i] - edge[i-(1<<y)];
			}
		}
		mst[i] = determinant(mat, m-1);
	}
	}
	//outa(mst, 0, N-1);
	
	mint ans = 0;
	rep(i, 1, N-2){
		int S = (N-1)-i, b = 1 << digitnum(S, 2)-1;
		for(int j = (S-1)&S; j >= b; j=(j-1)&S){
			int b = edge[i+j] - (edge[i] + edge[j]), c = edge[i+S-j] - (edge[i] + edge[S-j]);
			ans += mst[i] * mst[j] * mst[S-j] * (beki[b]-1) * (beki[c]-1);
		}
	}
	outl(ans*2);
	
	return 0;
}
accuracy: 0.2608695652173913
time_ms: 240
memory_kb: 4088
final_rank: 20

code_snippet: #pragma GCC ta g("avx2")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
using namespace std;
#define DEBUG
#ifdef DEBUG
template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << '(' << p.first << ',' << p.second << ')';
    return os;
}
template <class T> ostream &operator<<(ostream &os, const vector<T> &v) {
    os << '{';
    for(int i = 0; i < (int)v.size(); i++) {
        if(i) { os << ','; }
        os << v[i];
    }
    os << '}';
    return os;
}
void debugg() { cerr << endl; }
template <class T, class... Args>
void debugg(const T &x, const Args &... args) {
    cerr << " " << x;
    debugg(args...);
}
#define debug(...)                                                             \
    cerr << __LINE__ << " [" << #__VA_ARGS__ << "]: ", debugg(__VA_ARGS__)
#define dump(x) cerr << __LINE__ << " " << #x << " = " << (x) << endl
#else
#define debug(...) (void(0))
#define dump(x) (void(0))
#endif
using namespace std;
typedef long long ll;
typedef vector<ll> vl;
typedef vector<vl> vvl;
typedef vector<char> vc;
typedef vector<string> vs;
typedef vector<bool> vb;
typedef vector<double> vd;
typedef pair<ll,ll> P;
typedef pair<int,int> pii;
typedef vector<P> vpl;
typedef tuple<ll,ll,ll> tapu;
#define rep(i,n) for(int i=0; i<(n); i++)
#define REP(i,a,b) for(int i=(a); i<(b); i++)
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()
const int inf = (1<<30)-1;
const ll linf = 1LL<<61;
const int MAX = 510000;
int dy[8] = {0,1,0,-1,1,-1,-1,1};
int dx[8] = {-1,0,1,0,1,-1,1,-1};
const double pi = acos(-1);
const double eps = 1e-7;
template<typename T1,typename T2> inline bool chmin(T1 &a,T2 b){
	if(a>b){
		a = b; return true;
	}
	else return false;
}
template<typename T1,typename T2> inline bool chmax(T1 &a,T2 b){
	if(a<b){
		a = b; return true;
	}
	else return false;
}
template<typename T> inline void print(T &a){
    int sz = a.size();
    for(auto itr = a.begin(); itr != a.end(); itr++){
		cout << *itr;
        sz--;
        if(sz) cout << " ";
	}
    cout << "\n";
}
template<typename T1,typename T2> inline void print2(T1 a, T2 b){
	cout << a << " " << b << "\n";
}
template<typename T1,typename T2,typename T3> inline void print3(T1 a, T2 b, T3 c){
	cout << a << " " << b << " " << c << "\n";
}
void mark() {cout << "#" << "\n";}
ll pcount(ll x) {return __builtin_popcountll(x);}
//#include <atcoder/maxflow>
//using namespace atcoder;
//const int mod = 1e9 + 7;
const int mod = 998244353;

template <int mod> struct ModInt {
    int x;

    ModInt() : x(0) {}

    ModInt(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}

    ModInt &operator+=(const ModInt &p) {
        if((x += p.x) >= mod) x -= mod;
        return *this;
    }

    ModInt &operator-=(const ModInt &p) {
        if((x += mod - p.x) >= mod) x -= mod;
        return *this;
    }

    ModInt &operator*=(const ModInt &p) {
        x = (int)(1LL * x * p.x % mod);
        return *this;
    }

    ModInt &operator/=(const ModInt &p) {
        *this *= p.inverse();
        return *this;
    }

    ModInt operator-() const { return ModInt(-x); }

    ModInt operator+(const ModInt &p) const { return ModInt(*this) += p; }

    ModInt operator-(const ModInt &p) const { return ModInt(*this) -= p; }

    ModInt operator*(const ModInt &p) const { return ModInt(*this) *= p; }

    ModInt operator/(const ModInt &p) const { return ModInt(*this) /= p; }

    bool operator==(const ModInt &p) const { return x == p.x; }

    bool operator!=(const ModInt &p) const { return x != p.x; }

    ModInt inverse() const {
        int a = x, b = mod, u = 1, v = 0, t;
        while(b > 0) {
            t = a / b;
            swap(a -= t * b, b);
            swap(u -= t * v, v);
        }
        return ModInt(u);
    }

    ModInt pow(int64_t n) const {
        ModInt ret(1), mul(x);
        while(n > 0) {
            if(n & 1) ret *= mul;
            mul *= mul;
            n >>= 1;
        }
        return ret;
    }

    friend ostream &operator<<(ostream &os, const ModInt &p) {
        return os << p.x;
    }

    friend istream &operator>>(istream &is, ModInt &a) {
        int64_t t;
        is >> t;
        a = ModInt<mod>(t);
        return (is);
    }

    static int get_mod() { return mod; }
};

using mint = ModInt<mod>;

template< class T >
struct Matrix {
  vector< vector< T > > A;

  Matrix() {}

  Matrix(size_t n, size_t m) : A(n, vector< T >(m, 0)) {}

  Matrix(size_t n) : A(n, vector< T >(n, 0)) {};

  size_t height() const {
    return (A.size());
  }

  size_t width() const {
    return (A[0].size());
  }

  inline const vector< T > &operator[](int k) const {
    return (A.at(k));
  }

  inline vector< T > &operator[](int k) {
    return (A.at(k));
  }

  static Matrix I(size_t n) {
    Matrix mat(n);
    for(int i = 0; i < n; i++) mat[i][i] = 1;
    return (mat);
  }

  Matrix &operator+=(const Matrix &B) {
    size_t n = height(), m = width();
    assert(n == B.height() && m == B.width());
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        (*this)[i][j] += B[i][j];
    return (*this);
  }

  Matrix &operator-=(const Matrix &B) {
    size_t n = height(), m = width();
    assert(n == B.height() && m == B.width());
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        (*this)[i][j] -= B[i][j];
    return (*this);
  }

  Matrix &operator*=(const Matrix &B) {
    size_t n = height(), m = B.width(), p = width();
    assert(p == B.height());
    vector< vector< T > > C(n, vector< T >(m, 0));
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        for(int k = 0; k < p; k++)
          C[i][j] = (C[i][j] + (*this)[i][k] * B[k][j]);
    A.swap(C);
    return (*this);
  }

  Matrix &operator^=(long long k) {
    Matrix B = Matrix::I(height());
    while(k > 0) {
      if(k & 1) B *= *this;
      *this *= *this;
      k >>= 1LL;
    }
    A.swap(B.A);
    return (*this);
  }

  Matrix operator+(const Matrix &B) const {
    return (Matrix(*this) += B);
  }

  Matrix operator-(const Matrix &B) const {
    return (Matrix(*this) -= B);
  }

  Matrix operator*(const Matrix &B) const {
    return (Matrix(*this) *= B);
  }

  Matrix operator^(const long long k) const {
    return (Matrix(*this) ^= k);
  }

  friend ostream &operator<<(ostream &os, Matrix &p) {
    size_t n = p.height(), m = p.width();
    for(int i = 0; i < n; i++) {
      os << "[";
      for(int j = 0; j < m; j++) {
        os << p[i][j] << (j + 1 == m ? "]\n" : ",");
      }
    }
    return (os);
  }


  T determinant() {
    Matrix B(*this);
    assert(width() == height());
    T ret = 1;
    for(int i = 0; i < width(); i++) {
      int idx = -1;
      for(int j = i; j < width(); j++) {
        if(B[j][i] != 0) idx = j;
      }
      if(idx == -1) return (0);
      if(i != idx) {
        ret *= -1;
        swap(B[i], B[idx]);
      }
      ret *= B[i][i];
      T vv = B[i][i];
      for(int j = 0; j < width(); j++) {
        B[i][j] /= vv;
      }
      for(int j = i + 1; j < width(); j++) {
        T a = B[j][i];
        for(int k = 0; k < width(); k++) {
          B[j][k] -= B[i][k] * a;
        }
      }
    }
    return (ret);
  }
};

int G[17][17];
int cnt[1<<17];
mint zen[1<<17];
mint p[300];

int main(){
    int n,m; cin >> n >> m;
    rep(i,m){
        int a,b; cin >> a >> b; a--; b--;
        G[a][b] = G[b][a] = 1;
    }
    REP(bit,1,1<<n){
        if(pcount(bit) <= 1){
            zen[bit] = pcount(bit);
            continue;
        }
        vl v;
        rep(i,n) if(bit>>i & 1) v.push_back(i);
        int N = v.size();
        Matrix<mint> mat(N-1,N-1);
        vector<vector<int>> g(N,vector<int>(N));
        rep(i,N) rep(j,N){
            if(i==j) continue;
            g[j][j] += G[v[i]][v[j]];
            g[i][j] -= G[v[i]][v[j]];
            cnt[bit] += G[v[i]][v[j]];
        }
        cnt[bit] /= 2;
        REP(i,1,N) REP(j,1,N) mat[i-1][j-1] = g[i][j];
        zen[bit] = mat.determinant();
    }
    p[0] = 1;
    rep(i,299) p[i+1] = p[i] * 2;
    mint ans = 0;
    REP(bit,1,(1<<n)-1){
        int S = (1<<n) - 1 - bit;
        mint res = 0;
        for(int T=S-1; T>=0; T--){
            T &= S;
            //if(T < (S^T)) break;
            int U = T^S;
            int B = cnt[bit+T] - cnt[bit] - cnt[T], C = cnt[bit+U] - cnt[bit] - cnt[U];
            res += zen[T] * zen[U] * (p[B] - 1) * (p[C] - 1);
        }
        ans += res * zen[bit];
    }
    //ans *= 2;
    cout << ans << endl;
}
accuracy: 1
time_ms: 1240
memory_kb: 4400
final_rank: 6

code_snippet: #pragma GCC ta g("avx2")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
using namespace std;
#define DEBUG
#ifdef DEBUG
template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << '(' << p.first << ',' << p.second << ')';
    return os;
}
template <class T> ostream &operator<<(ostream &os, const vector<T> &v) {
    os << '{';
    for(int i = 0; i < (int)v.size(); i++) {
        if(i) { os << ','; }
        os << v[i];
    }
    os << '}';
    return os;
}
void debugg() { cerr << endl; }
template <class T, class... Args>
void debugg(const T &x, const Args &... args) {
    cerr << " " << x;
    debugg(args...);
}
#define debug(...)                                                             \
    cerr << __LINE__ << " [" << #__VA_ARGS__ << "]: ", debugg(__VA_ARGS__)
#define dump(x) cerr << __LINE__ << " " << #x << " = " << (x) << endl
#else
#define debug(...) (void(0))
#define dump(x) (void(0))
#endif
using namespace std;
typedef long long ll;
typedef vector<ll> vl;
typedef vector<vl> vvl;
typedef vector<char> vc;
typedef vector<string> vs;
typedef vector<bool> vb;
typedef vector<double> vd;
typedef pair<ll,ll> P;
typedef pair<int,int> pii;
typedef vector<P> vpl;
typedef tuple<ll,ll,ll> tapu;
#define rep(i,n) for(int i=0; i<(n); i++)
#define REP(i,a,b) for(int i=(a); i<(b); i++)
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()
const int inf = (1<<30)-1;
const ll linf = 1LL<<61;
const int MAX = 510000;
int dy[8] = {0,1,0,-1,1,-1,-1,1};
int dx[8] = {-1,0,1,0,1,-1,1,-1};
const double pi = acos(-1);
const double eps = 1e-7;
template<typename T1,typename T2> inline bool chmin(T1 &a,T2 b){
	if(a>b){
		a = b; return true;
	}
	else return false;
}
template<typename T1,typename T2> inline bool chmax(T1 &a,T2 b){
	if(a<b){
		a = b; return true;
	}
	else return false;
}
template<typename T> inline void print(T &a){
    int sz = a.size();
    for(auto itr = a.begin(); itr != a.end(); itr++){
		cout << *itr;
        sz--;
        if(sz) cout << " ";
	}
    cout << "\n";
}
template<typename T1,typename T2> inline void print2(T1 a, T2 b){
	cout << a << " " << b << "\n";
}
template<typename T1,typename T2,typename T3> inline void print3(T1 a, T2 b, T3 c){
	cout << a << " " << b << " " << c << "\n";
}
void mark() {cout << "#" << "\n";}
ll pcount(ll x) {return __builtin_popcountll(x);}
//#include <atcoder/maxflow>
//using namespace atcoder;
//const int mod = 1e9 + 7;
const int mod = 998244353;

template <int mod> struct ModInt {
    int x;

    ModInt() : x(0) {}

    ModInt(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}

    ModInt &operator+=(const ModInt &p) {
        if((x += p.x) >= mod) x -= mod;
        return *this;
    }

    ModInt &operator-=(const ModInt &p) {
        if((x += mod - p.x) >= mod) x -= mod;
        return *this;
    }

    ModInt &operator*=(const ModInt &p) {
        x = (int)(1LL * x * p.x % mod);
        return *this;
    }

    ModInt &operator/=(const ModInt &p) {
        *this *= p.inverse();
        return *this;
    }

    ModInt operator-() const { return ModInt(-x); }

    ModInt operator+(const ModInt &p) const { return ModInt(*this) += p; }

    ModInt operator-(const ModInt &p) const { return ModInt(*this) -= p; }

    ModInt operator*(const ModInt &p) const { return ModInt(*this) *= p; }

    ModInt operator/(const ModInt &p) const { return ModInt(*this) /= p; }

    bool operator==(const ModInt &p) const { return x == p.x; }

    bool operator!=(const ModInt &p) const { return x != p.x; }

    ModInt inverse() const {
        int a = x, b = mod, u = 1, v = 0, t;
        while(b > 0) {
            t = a / b;
            swap(a -= t * b, b);
            swap(u -= t * v, v);
        }
        return ModInt(u);
    }

    ModInt pow(int64_t n) const {
        ModInt ret(1), mul(x);
        while(n > 0) {
            if(n & 1) ret *= mul;
            mul *= mul;
            n >>= 1;
        }
        return ret;
    }

    friend ostream &operator<<(ostream &os, const ModInt &p) {
        return os << p.x;
    }

    friend istream &operator>>(istream &is, ModInt &a) {
        int64_t t;
        is >> t;
        a = ModInt<mod>(t);
        return (is);
    }

    static int get_mod() { return mod; }
};

using mint = ModInt<mod>;

template< class T >
struct Matrix {
  vector< vector< T > > A;

  Matrix() {}

  Matrix(size_t n, size_t m) : A(n, vector< T >(m, 0)) {}

  Matrix(size_t n) : A(n, vector< T >(n, 0)) {};

  size_t height() const {
    return (A.size());
  }

  size_t width() const {
    return (A[0].size());
  }

  inline const vector< T > &operator[](int k) const {
    return (A.at(k));
  }

  inline vector< T > &operator[](int k) {
    return (A.at(k));
  }

  static Matrix I(size_t n) {
    Matrix mat(n);
    for(int i = 0; i < n; i++) mat[i][i] = 1;
    return (mat);
  }

  Matrix &operator+=(const Matrix &B) {
    size_t n = height(), m = width();
    assert(n == B.height() && m == B.width());
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        (*this)[i][j] += B[i][j];
    return (*this);
  }

  Matrix &operator-=(const Matrix &B) {
    size_t n = height(), m = width();
    assert(n == B.height() && m == B.width());
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        (*this)[i][j] -= B[i][j];
    return (*this);
  }

  Matrix &operator*=(const Matrix &B) {
    size_t n = height(), m = B.width(), p = width();
    assert(p == B.height());
    vector< vector< T > > C(n, vector< T >(m, 0));
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        for(int k = 0; k < p; k++)
          C[i][j] = (C[i][j] + (*this)[i][k] * B[k][j]);
    A.swap(C);
    return (*this);
  }

  Matrix &operator^=(long long k) {
    Matrix B = Matrix::I(height());
    while(k > 0) {
      if(k & 1) B *= *this;
      *this *= *this;
      k >>= 1LL;
    }
    A.swap(B.A);
    return (*this);
  }

  Matrix operator+(const Matrix &B) const {
    return (Matrix(*this) += B);
  }

  Matrix operator-(const Matrix &B) const {
    return (Matrix(*this) -= B);
  }

  Matrix operator*(const Matrix &B) const {
    return (Matrix(*this) *= B);
  }

  Matrix operator^(const long long k) const {
    return (Matrix(*this) ^= k);
  }

  friend ostream &operator<<(ostream &os, Matrix &p) {
    size_t n = p.height(), m = p.width();
    for(int i = 0; i < n; i++) {
      os << "[";
      for(int j = 0; j < m; j++) {
        os << p[i][j] << (j + 1 == m ? "]\n" : ",");
      }
    }
    return (os);
  }


  T determinant() {
    Matrix B(*this);
    assert(width() == height());
    T ret = 1;
    for(int i = 0; i < width(); i++) {
      int idx = -1;
      for(int j = i; j < width(); j++) {
        if(B[j][i] != 0) idx = j;
      }
      if(idx == -1) return (0);
      if(i != idx) {
        ret *= -1;
        swap(B[i], B[idx]);
      }
      ret *= B[i][i];
      T vv = B[i][i];
      for(int j = 0; j < width(); j++) {
        B[i][j] /= vv;
      }
      for(int j = i + 1; j < width(); j++) {
        T a = B[j][i];
        for(int k = 0; k < width(); k++) {
          B[j][k] -= B[i][k] * a;
        }
      }
    }
    return (ret);
  }
};

int G[17][17];
int cnt[1<<17];
mint zen[1<<17];
mint p[300];

int main(){
    int n,m; cin >> n >> m;
    rep(i,m){
        int a,b; cin >> a >> b; a--; b--;
        G[a][b] = G[b][a] = 1;
    }
    REP(bit,1,1<<n){
        if(pcount(bit) <= 1){
            zen[bit] = pcount(bit);
            continue;
        }
        vl v;
        rep(i,n) if(bit>>i & 1) v.push_back(i);
        int N = v.size();
        Matrix<mint> mat(N-1,N-1);
        vector<vector<int>> g(N,vector<int>(N));
        rep(i,N) rep(j,N){
            if(i==j) continue;
            g[j][j] += G[v[i]][v[j]];
            g[i][j] -= G[v[i]][v[j]];
            cnt[bit] += G[v[i]][v[j]];
        }
        cnt[bit] /= 2;
        REP(i,1,N) REP(j,1,N) mat[i-1][j-1] = g[i][j];
        zen[bit] = mat.determinant();
    }
    p[0] = 1;
    rep(i,299) p[i+1] = p[i] * 2;
    mint ans = 0;
    REP(bit,1,(1<<n)-1){
        int S = (1<<n) - 1 - bit;
        mint res = 0;
        for(int T=S-1; T>=0; T--){
            T &= S;
            if(T < (S^T)) break;
            int U = T^S;
            int B = cnt[bit+T] - cnt[bit] - cnt[T], C = cnt[bit+U] - cnt[bit] - cnt[U];
            res += zen[T] * zen[U] * (p[B] - 1) * (p[C] - 1);
        }
        ans += res * zen[bit];
    }
    ans *= 2;
    cout << ans << endl;
}
accuracy: 1
time_ms: 890
memory_kb: 4472
final_rank: 1

code_snippet: #pragma GCC ta g("avx2")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
using namespace std;
#define DEBUG
#ifdef DEBUG
template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << '(' << p.first << ',' << p.second << ')';
    return os;
}
template <class T> ostream &operator<<(ostream &os, const vector<T> &v) {
    os << '{';
    for(int i = 0; i < (int)v.size(); i++) {
        if(i) { os << ','; }
        os << v[i];
    }
    os << '}';
    return os;
}
void debugg() { cerr << endl; }
template <class T, class... Args>
void debugg(const T &x, const Args &... args) {
    cerr << " " << x;
    debugg(args...);
}
#define debug(...)                                                             \
    cerr << __LINE__ << " [" << #__VA_ARGS__ << "]: ", debugg(__VA_ARGS__)
#define dump(x) cerr << __LINE__ << " " << #x << " = " << (x) << endl
#else
#define debug(...) (void(0))
#define dump(x) (void(0))
#endif
using namespace std;
typedef long long ll;
typedef vector<ll> vl;
typedef vector<vl> vvl;
typedef vector<char> vc;
typedef vector<string> vs;
typedef vector<bool> vb;
typedef vector<double> vd;
typedef pair<ll,ll> P;
typedef pair<int,int> pii;
typedef vector<P> vpl;
typedef tuple<ll,ll,ll> tapu;
#define rep(i,n) for(int i=0; i<(n); i++)
#define REP(i,a,b) for(int i=(a); i<(b); i++)
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()
const int inf = (1<<30)-1;
const ll linf = 1LL<<61;
const int MAX = 510000;
int dy[8] = {0,1,0,-1,1,-1,-1,1};
int dx[8] = {-1,0,1,0,1,-1,1,-1};
const double pi = acos(-1);
const double eps = 1e-7;
template<typename T1,typename T2> inline bool chmin(T1 &a,T2 b){
	if(a>b){
		a = b; return true;
	}
	else return false;
}
template<typename T1,typename T2> inline bool chmax(T1 &a,T2 b){
	if(a<b){
		a = b; return true;
	}
	else return false;
}
template<typename T> inline void print(T &a){
    int sz = a.size();
    for(auto itr = a.begin(); itr != a.end(); itr++){
		cout << *itr;
        sz--;
        if(sz) cout << " ";
	}
    cout << "\n";
}
template<typename T1,typename T2> inline void print2(T1 a, T2 b){
	cout << a << " " << b << "\n";
}
template<typename T1,typename T2,typename T3> inline void print3(T1 a, T2 b, T3 c){
	cout << a << " " << b << " " << c << "\n";
}
void mark() {cout << "#" << "\n";}
ll pcount(ll x) {return __builtin_popcountll(x);}
//#include <atcoder/maxflow>
//using namespace atcoder;
//const int mod = 1e9 + 7;
const int mod = 998244353;

template <int mod> struct ModInt {
    int x;

    ModInt() : x(0) {}

    ModInt(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}

    ModInt &operator+=(const ModInt &p) {
        if((x += p.x) >= mod) x -= mod;
        return *this;
    }

    ModInt &operator-=(const ModInt &p) {
        if((x += mod - p.x) >= mod) x -= mod;
        return *this;
    }

    ModInt &operator*=(const ModInt &p) {
        x = (int)(1LL * x * p.x % mod);
        return *this;
    }

    ModInt &operator/=(const ModInt &p) {
        *this *= p.inverse();
        return *this;
    }

    ModInt operator-() const { return ModInt(-x); }

    ModInt operator+(const ModInt &p) const { return ModInt(*this) += p; }

    ModInt operator-(const ModInt &p) const { return ModInt(*this) -= p; }

    ModInt operator*(const ModInt &p) const { return ModInt(*this) *= p; }

    ModInt operator/(const ModInt &p) const { return ModInt(*this) /= p; }

    bool operator==(const ModInt &p) const { return x == p.x; }

    bool operator!=(const ModInt &p) const { return x != p.x; }

    ModInt inverse() const {
        int a = x, b = mod, u = 1, v = 0, t;
        while(b > 0) {
            t = a / b;
            swap(a -= t * b, b);
            swap(u -= t * v, v);
        }
        return ModInt(u);
    }

    ModInt pow(int64_t n) const {
        ModInt ret(1), mul(x);
        while(n > 0) {
            if(n & 1) ret *= mul;
            mul *= mul;
            n >>= 1;
        }
        return ret;
    }

    friend ostream &operator<<(ostream &os, const ModInt &p) {
        return os << p.x;
    }

    friend istream &operator>>(istream &is, ModInt &a) {
        int64_t t;
        is >> t;
        a = ModInt<mod>(t);
        return (is);
    }

    static int get_mod() { return mod; }
};

using mint = ModInt<mod>;

template< class T >
struct Matrix {
  vector< vector< T > > A;

  Matrix() {}

  Matrix(size_t n, size_t m) : A(n, vector< T >(m, 0)) {}

  Matrix(size_t n) : A(n, vector< T >(n, 0)) {};

  size_t height() const {
    return (A.size());
  }

  size_t width() const {
    return (A[0].size());
  }

  inline const vector< T > &operator[](int k) const {
    return (A.at(k));
  }

  inline vector< T > &operator[](int k) {
    return (A.at(k));
  }

  static Matrix I(size_t n) {
    Matrix mat(n);
    for(int i = 0; i < n; i++) mat[i][i] = 1;
    return (mat);
  }

  Matrix &operator+=(const Matrix &B) {
    size_t n = height(), m = width();
    assert(n == B.height() && m == B.width());
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        (*this)[i][j] += B[i][j];
    return (*this);
  }

  Matrix &operator-=(const Matrix &B) {
    size_t n = height(), m = width();
    assert(n == B.height() && m == B.width());
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        (*this)[i][j] -= B[i][j];
    return (*this);
  }

  Matrix &operator*=(const Matrix &B) {
    size_t n = height(), m = B.width(), p = width();
    assert(p == B.height());
    vector< vector< T > > C(n, vector< T >(m, 0));
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        for(int k = 0; k < p; k++)
          C[i][j] = (C[i][j] + (*this)[i][k] * B[k][j]);
    A.swap(C);
    return (*this);
  }

  Matrix &operator^=(long long k) {
    Matrix B = Matrix::I(height());
    while(k > 0) {
      if(k & 1) B *= *this;
      *this *= *this;
      k >>= 1LL;
    }
    A.swap(B.A);
    return (*this);
  }

  Matrix operator+(const Matrix &B) const {
    return (Matrix(*this) += B);
  }

  Matrix operator-(const Matrix &B) const {
    return (Matrix(*this) -= B);
  }

  Matrix operator*(const Matrix &B) const {
    return (Matrix(*this) *= B);
  }

  Matrix operator^(const long long k) const {
    return (Matrix(*this) ^= k);
  }

  friend ostream &operator<<(ostream &os, Matrix &p) {
    size_t n = p.height(), m = p.width();
    for(int i = 0; i < n; i++) {
      os << "[";
      for(int j = 0; j < m; j++) {
        os << p[i][j] << (j + 1 == m ? "]\n" : ",");
      }
    }
    return (os);
  }


  T determinant() {
    Matrix B(*this);
    assert(width() == height());
    T ret = 1;
    for(int i = 0; i < width(); i++) {
      int idx = -1;
      for(int j = i; j < width(); j++) {
        if(B[j][i] != 0) idx = j;
      }
      if(idx == -1) return (0);
      if(i != idx) {
        ret *= -1;
        swap(B[i], B[idx]);
      }
      ret *= B[i][i];
      T vv = B[i][i];
      for(int j = 0; j < width(); j++) {
        B[i][j] /= vv;
      }
      for(int j = i + 1; j < width(); j++) {
        T a = B[j][i];
        for(int k = 0; k < width(); k++) {
          B[j][k] -= B[i][k] * a;
        }
      }
    }
    return (ret);
  }
};

int G[17][17];
mint zen[1<<17];
mint p[300];

int main(){
    int n,m; cin >> n >> m;
    rep(i,m){
        int a,b; cin >> a >> b; a--; b--;
        G[a][b] = G[b][a] = 1;
    }
    REP(bit,1,1<<n){
        if(pcount(bit) <= 1){
            zen[bit] = pcount(bit);
            continue;
        }
        vl v;
        rep(i,n) if(bit>>i & 1) v.push_back(i);
        int N = v.size();
        Matrix<mint> mat(N-1,N-1);
        vector<vector<int>> g(N,vector<int>(N));
        rep(i,N) rep(j,N){
            if(i==j) continue;
            g[j][j] += G[v[i]][v[j]];
            g[i][j] -= G[v[i]][v[j]];
        }
        REP(i,1,N) REP(j,1,N) mat[i-1][j-1] = g[i][j]; 
        zen[bit] = mat.determinant();
    }
    p[0] = 1;
    rep(i,299) p[i+1] = p[i] * 2;
    mint ans = 0;
    REP(bit,1,(1<<n)-1){
        int S = (1<<n) - 1 - bit;
        vl cnt(n,0);
        vl v;
        rep(i,n){
            if(bit>>i & 1) continue;
            v.push_back(i);
            rep(j,n){
                if(bit>>j & 1 ^ 1) continue;
                cnt[i] += G[i][j];
            }
        }
        mint res = 0;
        for(int T=S-1; T>=0; T--){
            int B = 0, C = 0;
            T &= S;
            if(T < (S^T)) break;
            for(auto x : v){
                if(T>>x & 1) B += cnt[x];
                else C += cnt[x];
            }
            res += zen[T] * zen[S^T] * (p[B] - 1) * (p[C] - 1);
        }
        ans += res * zen[bit];
    }
    ans *= 2;
    cout << ans << endl;
}
accuracy: 1
time_ms: 2090
memory_kb: 3960
final_rank: 13

code_snippet: #include <iostream>
#include <utility>
#include <vector>


#include <cassert>
#include <numeric>
#include <type_traits>

#ifdef _MSC_VER
#include <intrin.h>
#endif


#include <utility>

#ifdef _MSC_VER
#include <intrin.h>
#endif

namespace atcoder {

namespace internal {

constexpr long long safe_mod(long long x, long long m) {
    x %= m;
    if (x < 0) x += m;
    return x;
}

struct barrett {
    unsigned int _m;
    unsigned long long im;

    barrett(unsigned int m) : _m(m), im((unsigned long long)(-1) / m + 1) {}

    unsigned int umod() const { return _m; }

    unsigned int mul(unsigned int a, unsigned int b) const {

        unsigned long long z = a;
        z *= b;
#ifdef _MSC_VER
        unsigned long long x;
        _umul128(z, im, &x);
#else
        unsigned long long x =
            (unsigned long long)(((unsigned __int128)(z)*im) >> 64);
#endif
        unsigned int v = (unsigned int)(z - x * _m);
        if (_m <= v) v += _m;
        return v;
    }
};

constexpr long long pow_mod_constexpr(long long x, long long n, int m) {
    if (m == 1) return 0;
    unsigned int _m = (unsigned int)(m);
    unsigned long long r = 1;
    unsigned long long y = safe_mod(x, m);
    while (n) {
        if (n & 1) r = (r * y) % _m;
        y = (y * y) % _m;
        n >>= 1;
    }
    return r;
}

constexpr bool is_prime_constexpr(int n) {
    if (n <= 1) return false;
    if (n == 2 || n == 7 || n == 61) return true;
    if (n % 2 == 0) return false;
    long long d = n - 1;
    while (d % 2 == 0) d /= 2;
    constexpr long long bases[3] = {2, 7, 61};
    for (long long a : bases) {
        long long t = d;
        long long y = pow_mod_constexpr(a, t, n);
        while (t != n - 1 && y != 1 && y != n - 1) {
            y = y * y % n;
            t <<= 1;
        }
        if (y != n - 1 && t % 2 == 0) {
            return false;
        }
    }
    return true;
}
template <int n> constexpr bool is_prime = is_prime_constexpr(n);

constexpr std::pair<long long, long long> inv_gcd(long long a, long long b) {
    a = safe_mod(a, b);
    if (a == 0) return {b, 0};

    long long s = b, t = a;
    long long m0 = 0, m1 = 1;

    while (t) {
        long long u = s / t;
        s -= t * u;
        m0 -= m1 * u;  // |m1 * u| <= |m1| * s <= b


        auto tmp = s;
        s = t;
        t = tmp;
        tmp = m0;
        m0 = m1;
        m1 = tmp;
    }
    if (m0 < 0) m0 += b / s;
    return {s, m0};
}

constexpr int primitive_root_constexpr(int m) {
    if (m == 2) return 1;
    if (m == 167772161) return 3;
    if (m == 469762049) return 3;
    if (m == 754974721) return 11;
    if (m == 998244353) return 3;
    int divs[20] = {};
    divs[0] = 2;
    int cnt = 1;
    int x = (m - 1) / 2;
    while (x % 2 == 0) x /= 2;
    for (int i = 3; (long long)(i)*i <= x; i += 2) {
        if (x % i == 0) {
            divs[cnt++] = i;
            while (x % i == 0) {
                x /= i;
            }
        }
    }
    if (x > 1) {
        divs[cnt++] = x;
    }
    for (int g = 2;; g++) {
        bool ok = true;
        for (int i = 0; i < cnt; i++) {
            if (pow_mod_constexpr(g, (m - 1) / divs[i], m) == 1) {
                ok = false;
                break;
            }
        }
        if (ok) return g;
    }
}
template <int m> constexpr int primitive_root = primitive_root_constexpr(m);

}  // namespace internal

}  // namespace atcoder


#include <cassert>
#include <numeric>
#include <type_traits>

namespace atcoder {

namespace internal {

#ifndef _MSC_VER
template <class T>
using is_signed_int128 =
    typename std::conditional<std::is_same<T, __int128_t>::value ||
                                  std::is_same<T, __int128>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using is_unsigned_int128 =
    typename std::conditional<std::is_same<T, __uint128_t>::value ||
                                  std::is_same<T, unsigned __int128>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using make_unsigned_int128 =
    typename std::conditional<std::is_same<T, __int128_t>::value,
                              __uint128_t,
                              unsigned __int128>;

template <class T>
using is_integral = typename std::conditional<std::is_integral<T>::value ||
                                                  is_signed_int128<T>::value ||
                                                  is_unsigned_int128<T>::value,
                                              std::true_type,
                                              std::false_type>::type;

template <class T>
using is_signed_int = typename std::conditional<(is_integral<T>::value &&
                                                 std::is_signed<T>::value) ||
                                                    is_signed_int128<T>::value,
                                                std::true_type,
                                                std::false_type>::type;

template <class T>
using is_unsigned_int =
    typename std::conditional<(is_integral<T>::value &&
                               std::is_unsigned<T>::value) ||
                                  is_unsigned_int128<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using to_unsigned = typename std::conditional<
    is_signed_int128<T>::value,
    make_unsigned_int128<T>,
    typename std::conditional<std::is_signed<T>::value,
                              std::make_unsigned<T>,
                              std::common_type<T>>::type>::type;

#else

template <class T> using is_integral = typename std::is_integral<T>;

template <class T>
using is_signed_int =
    typename std::conditional<is_integral<T>::value && std::is_signed<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using is_unsigned_int =
    typename std::conditional<is_integral<T>::value &&
                                  std::is_unsigned<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using to_unsigned = typename std::conditional<is_signed_int<T>::value,
                                              std::make_unsigned<T>,
                                              std::common_type<T>>::type;

#endif

template <class T>
using is_signed_int_t = std::enable_if_t<is_signed_int<T>::value>;

template <class T>
using is_unsigned_int_t = std::enable_if_t<is_unsigned_int<T>::value>;

template <class T> using to_unsigned_t = typename to_unsigned<T>::type;

}  // namespace internal

}  // namespace atcoder


namespace atcoder {

namespace internal {

struct modint_base {};
struct static_modint_base : modint_base {};

template <class T> using is_modint = std::is_base_of<modint_base, T>;
template <class T> using is_modint_t = std::enable_if_t<is_modint<T>::value>;

}  // namespace internal

template <int m, std::enable_if_t<(1 <= m)>* = nullptr>
struct static_modint : internal::static_modint_base {
    using mint = static_modint;

  public:
    static constexpr int mod() { return m; }
    static mint raw(int v) {
        mint x;
        x._v = v;
        return x;
    }

    static_modint() : _v(0) {}
    template <class T, internal::is_signed_int_t<T>* = nullptr>
    static_modint(T v) {
        long long x = (long long)(v % (long long)(umod()));
        if (x < 0) x += umod();
        _v = (unsigned int)(x);
    }
    template <class T, internal::is_unsigned_int_t<T>* = nullptr>
    static_modint(T v) {
        _v = (unsigned int)(v % umod());
    }

    unsigned int val() const { return _v; }

    mint& operator++() {
        _v++;
        if (_v == umod()) _v = 0;
        return *this;
    }
    mint& operator--() {
        if (_v == 0) _v = umod();
        _v--;
        return *this;
    }
    mint operator++(int) {
        mint result = *this;
        ++*this;
        return result;
    }
    mint operator--(int) {
        mint result = *this;
        --*this;
        return result;
    }

    mint& operator+=(const mint& rhs) {
        _v += rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator-=(const mint& rhs) {
        _v -= rhs._v;
        if (_v >= umod()) _v += umod();
        return *this;
    }
    mint& operator*=(const mint& rhs) {
        unsigned long long z = _v;
        z *= rhs._v;
        _v = (unsigned int)(z % umod());
        return *this;
    }
    mint& operator/=(const mint& rhs) { return *this = *this * rhs.inv(); }

    mint operator+() const { return *this; }
    mint operator-() const { return mint() - *this; }

    mint pow(long long n) const {
        assert(0 <= n);
        mint x = *this, r = 1;
        while (n) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        return r;
    }
    mint inv() const {
        if (prime) {
            assert(_v);
            return pow(umod() - 2);
        } else {
            auto eg = internal::inv_gcd(_v, m);
            assert(eg.first == 1);
            return eg.second;
        }
    }

    friend mint operator+(const mint& lhs, const mint& rhs) {
        return mint(lhs) += rhs;
    }
    friend mint operator-(const mint& lhs, const mint& rhs) {
        return mint(lhs) -= rhs;
    }
    friend mint operator*(const mint& lhs, const mint& rhs) {
        return mint(lhs) *= rhs;
    }
    friend mint operator/(const mint& lhs, const mint& rhs) {
        return mint(lhs) /= rhs;
    }
    friend bool operator==(const mint& lhs, const mint& rhs) {
        return lhs._v == rhs._v;
    }
    friend bool operator!=(const mint& lhs, const mint& rhs) {
        return lhs._v != rhs._v;
    }

  private:
    unsigned int _v;
    static constexpr unsigned int umod() { return m; }
    static constexpr bool prime = internal::is_prime<m>;
};

template <int id> struct dynamic_modint : internal::modint_base {
    using mint = dynamic_modint;

  public:
    static int mod() { return (int)(bt.umod()); }
    static void set_mod(int m) {
        assert(1 <= m);
        bt = internal::barrett(m);
    }
    static mint raw(int v) {
        mint x;
        x._v = v;
        return x;
    }

    dynamic_modint() : _v(0) {}
    template <class T, internal::is_signed_int_t<T>* = nullptr>
    dynamic_modint(T v) {
        long long x = (long long)(v % (long long)(mod()));
        if (x < 0) x += mod();
        _v = (unsigned int)(x);
    }
    template <class T, internal::is_unsigned_int_t<T>* = nullptr>
    dynamic_modint(T v) {
        _v = (unsigned int)(v % mod());
    }

    unsigned int val() const { return _v; }

    mint& operator++() {
        _v++;
        if (_v == umod()) _v = 0;
        return *this;
    }
    mint& operator--() {
        if (_v == 0) _v = umod();
        _v--;
        return *this;
    }
    mint operator++(int) {
        mint result = *this;
        ++*this;
        return result;
    }
    mint operator--(int) {
        mint result = *this;
        --*this;
        return result;
    }

    mint& operator+=(const mint& rhs) {
        _v += rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator-=(const mint& rhs) {
        _v += mod() - rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator*=(const mint& rhs) {
        _v = bt.mul(_v, rhs._v);
        return *this;
    }
    mint& operator/=(const mint& rhs) { return *this = *this * rhs.inv(); }

    mint operator+() const { return *this; }
    mint operator-() const { return mint() - *this; }

    mint pow(long long n) const {
        assert(0 <= n);
        mint x = *this, r = 1;
        while (n) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        return r;
    }
    mint inv() const {
        auto eg = internal::inv_gcd(_v, mod());
        assert(eg.first == 1);
        return eg.second;
    }

    friend mint operator+(const mint& lhs, const mint& rhs) {
        return mint(lhs) += rhs;
    }
    friend mint operator-(const mint& lhs, const mint& rhs) {
        return mint(lhs) -= rhs;
    }
    friend mint operator*(const mint& lhs, const mint& rhs) {
        return mint(lhs) *= rhs;
    }
    friend mint operator/(const mint& lhs, const mint& rhs) {
        return mint(lhs) /= rhs;
    }
    friend bool operator==(const mint& lhs, const mint& rhs) {
        return lhs._v == rhs._v;
    }
    friend bool operator!=(const mint& lhs, const mint& rhs) {
        return lhs._v != rhs._v;
    }

  private:
    unsigned int _v;
    static internal::barrett bt;
    static unsigned int umod() { return bt.umod(); }
};
template <int id> internal::barrett dynamic_modint<id>::bt = 998244353;

using modint998244353 = static_modint<998244353>;
using modint1000000007 = static_modint<1000000007>;
using modint = dynamic_modint<-1>;

namespace internal {

template <class T>
using is_static_modint = std::is_base_of<internal::static_modint_base, T>;

template <class T>
using is_static_modint_t = std::enable_if_t<is_static_modint<T>::value>;

template <class> struct is_dynamic_modint : public std::false_type {};
template <int id>
struct is_dynamic_modint<dynamic_modint<id>> : public std::true_type {};

template <class T>
using is_dynamic_modint_t = std::enable_if_t<is_dynamic_modint<T>::value>;

}  // namespace internal

}  // namespace atcoder


using mint = atcoder::modint998244353;

mint det(std::vector<std::vector<mint>> a) {
  const int n = a.size();
  mint ret = 1;
  for (int i = 0; i < n; i++) {
    for (int j = i; j < n; j++) {
      if (a[j][i] != mint(0)) {
        if (i != j) {
          std::swap(a[i], a[j]);
          ret = -ret;
        }
        break;
      }
    }
    if (a[i][i] == mint(0)) {
      return mint(0);
    }
    ret *= a[i][i];
    {
      const mint c = mint(1) / a[i][i];
      for (int j = i; j < n; j++) {
        a[j][i] *= c;
      }
    }
    for (int j = i + 1; j < n; j++) {
      const mint c = a[j][i];
      for (int k = i; k < n; k++) {
        a[j][k] -= c * a[i][k];
      }
    }
  }
  return ret;
}

int popcnt(int s) {
  int r = 0;
  while (s) {
    r += 1;
    s &= s - 1;
  }
  return r;
}

std::vector<std::vector<mint>> ranked_zeta(const std::vector<mint> &a) {
  const int n = a.size();
  const int h = popcnt(n - 1);
  assert(1 << h == n);

  std::vector ra(n, std::vector<mint>(h + 1, 0));
  for (int s = 0; s < n; s++) {
    ra[s][popcnt(s)] = a[s];
  }

  for (int w = 1; w < n; w *= 2) {
    for (int i = 0; i < n; i += 2 * w) {
      for (int k = 0; k < w; k += 1) {
        for (int t = 0; t < h + 1; t++) {
          ra[i + w + k][t] += ra[i + k][t];
        }
      }
    }
  }

  return ra;
}

std::vector<mint> subset_convolution(const std::vector<mint> &a,
                                     const std::vector<mint> &b) {
  const int n = a.size();
  const int h = popcnt(n - 1);
  assert(1 << h == n);

  auto ra = ranked_zeta(a), rb = ranked_zeta(b);
  std::vector rc(n, std::vector<mint>(h + 1, 0));
  for (int s = 0; s < n; s++) {
    for (int i = 0; i < h + 1; i++) {
      for (int j = 0; j < i + 1; j++) {
        rc[s][i] += ra[s][j] * rb[s][i - j];
      }
    }
  }

  for (int w = 1; w < n; w *= 2) {
    for (int i = 0; i < n; i += 2 * w) {
      for (int k = 0; k < w; k += 1) {
        for (int t = 0; t < h + 1; t++) {
          rc[i + w + k][t] -= rc[i + k][t];
        }
      }
    }
  }

  std::vector<mint> c(n);
  for (int s = 0; s < n; s++) {
    c[s] = rc[s][popcnt(s)];
  }

  return c;
}

int main() {
  int N, M;
  std::cin >> N >> M;

  std::vector<std::pair<int, int>> edges(M);
  for (auto &[a, b] : edges) {
    std::cin >> a >> b;
    a -= 1;
    b -= 1;
  }

  std::vector<mint> spt(1 << N, 0);
  for (int s = 1; s < 1 << N; s++) {
    std::vector<int> id(N, -1);
    int c = 0;
    for (int i = 0; i < N; i++) {
      if (s >> i & 1) {
        id[i] = c;
        c++;
      }
    }
    if (c + 2 > N) {
      continue;
    }
    std::vector lap(c - 1, std::vector<mint>(c - 1, 0));
    for (auto [a, b] : edges) {
      a = id[a];
      b = id[b];
      if (a != -1 && b != -1) {
        if (a < c - 1) {
          lap[a][a] += 1;
        }
        if (b < c - 1) {
          lap[b][b] += 1;
        }
        if (a < c - 1 && b < c - 1) {
          lap[a][b] -= 1;
          lap[b][a] -= 1;
        }
      }
    }
    spt[s] = det(std::move(lap));
  }

  std::vector<int> count(1 << N, 0);
  for (int s = 0; s < 1 << N; s++) {
    for (const auto &[a, b] : edges) {
      if (s >> a & 1 && s >> b & 1) {
        count[s] += 1;
      }
    }
  }

  mint ans = 0;
  for (int t = 0; t < 4; t++) {
    auto b = spt, c = spt, d = spt;
    for (int s = 0; s < 1 << N; s++) {
      if (!(t >> 0 & 1)) {
        b[s] /= mint(2).pow(count[s]);
        c[s] /= mint(2).pow(count[s]);
        d[s] *= mint(2).pow(count[(1 << N) - 1 - s]);
      }
      if (!(t >> 1 & 1)) {
        d[s] /= mint(2).pow(count[s]);
        c[s] /= mint(2).pow(count[s]);
        b[s] *= mint(2).pow(count[(1 << N) - 1 - s]);
      }
    }
    mint temp = subset_convolution(subset_convolution(b, c), d).back();
    if (popcnt(t) % 2 == 1) {
      temp *= -1;
    }
    ans += temp;
  }

  std::cout << ans.val() << "\n";

  return 0;
}
accuracy: 1
time_ms: 1090
memory_kb: 46776
final_rank: 19

code_snippet: #include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <chrono>
#include <cmath>
#include <complex>
#include <deque>
#include <forward_list>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iostream>
#include <limits>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
using lint = long long;
using pint = pair<int, int>;
using plint = pair<lint, lint>;
struct fast_ios { fast_ios(){ cin.tie(nullptr), ios::sync_with_stdio(false), cout << fixed << setprecision(20); }; } fast_ios_;
#define ALL(x) (x).begin(), (x).end()
#define FOR(i, begin, end) for(int i=(begin),i##_end_=(end);i<i##_end_;i++)
#define IFOR(i, begin, end) for(int i=(end)-1,i##_begin_=(begin);i>=i##_begin_;i--)
#define REP(i, n) FOR(i,0,n)
#define IREP(i, n) IFOR(i,0,n)
template <typename T, typename V>
void ndarray(vector<T>& vec, const V& val, int len) { vec.assign(len, val); }
template <typename T, typename V, typename... Args> void ndarray(vector<T>& vec, const V& val, int len, Args... args) { vec.resize(len), for_each(begin(vec), end(vec), [&](T& v) { ndarray(v, val, args...); }); }
template <typename T> bool chmax(T &m, const T q) { return m < q ? (m = q, true) : false; }
template <typename T> bool chmin(T &m, const T q) { return m > q ? (m = q, true) : false; }
int floor_lg(long long x) { return x <= 0 ? -1 : 63 - __builtin_clzll(x); }
template <typename T1, typename T2> pair<T1, T2> operator+(const pair<T1, T2> &l, const pair<T1, T2> &r) { return make_pair(l.first + r.first, l.second + r.second); }
template <typename T1, typename T2> pair<T1, T2> operator-(const pair<T1, T2> &l, const pair<T1, T2> &r) { return make_pair(l.first - r.first, l.second - r.second); }
template <typename T> vector<T> sort_unique(vector<T> vec) { sort(vec.begin(), vec.end()), vec.erase(unique(vec.begin(), vec.end()), vec.end()); return vec; }
template <typename T> int arglb(const std::vector<T> &v, const T &x) { return std::distance(v.begin(), std::lower_bound(v.begin(), v.end(), x)); }
template <typename T> int argub(const std::vector<T> &v, const T &x) { return std::distance(v.begin(), std::upper_bound(v.begin(), v.end(), x)); }
template <typename T> istream &operator>>(istream &is, vector<T> &vec) { for (auto &v : vec) is >> v; return is; }
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec) { os << '['; for (auto v : vec) os << v << ','; os << ']'; return os; }
template <typename T, size_t sz> ostream &operator<<(ostream &os, const array<T, sz> &arr) { os << '['; for (auto v : arr) os << v << ','; os << ']'; return os; }
#if __cplusplus >= 201703L
template <typename... T> istream &operator>>(istream &is, tuple<T...> &tpl) { std::apply([&is](auto &&... args) { ((is >> args), ...);}, tpl); return is; }
template <typename... T> ostream &operator<<(ostream &os, const tuple<T...> &tpl) { os << '('; std::apply([&os](auto &&... args) { ((os << args << ','), ...);}, tpl); return os << ')'; }
#endif
template <typename T> ostream &operator<<(ostream &os, const deque<T> &vec) { os << "deq["; for (auto v : vec) os << v << ','; os << ']'; return os; }
template <typename T> ostream &operator<<(ostream &os, const set<T> &vec) { os << '{'; for (auto v : vec) os << v << ','; os << '}'; return os; }
template <typename T, typename TH> ostream &operator<<(ostream &os, const unordered_set<T, TH> &vec) { os << '{'; for (auto v : vec) os << v << ','; os << '}'; return os; }
template <typename T> ostream &operator<<(ostream &os, const multiset<T> &vec) { os << '{'; for (auto v : vec) os << v << ','; os << '}'; return os; }
template <typename T> ostream &operator<<(ostream &os, const unordered_multiset<T> &vec) { os << '{'; for (auto v : vec) os << v << ','; os << '}'; return os; }
template <typename T1, typename T2> ostream &operator<<(ostream &os, const pair<T1, T2> &pa) { os << '(' << pa.first << ',' << pa.second << ')'; return os; }
template <typename TK, typename TV> ostream &operator<<(ostream &os, const map<TK, TV> &mp) { os << '{'; for (auto v : mp) os << v.first << "=>" << v.second << ','; os << '}'; return os; }
template <typename TK, typename TV, typename TH> ostream &operator<<(ostream &os, const unordered_map<TK, TV, TH> &mp) { os << '{'; for (auto v : mp) os << v.first << "=>" << v.second << ','; os << '}'; return os; }
#ifdef HITONANODE_LOCAL
const string COLOR_RESET = "\033[0m", BRIGHT_GREEN = "\033[1;32m", BRIGHT_RED = "\033[1;31m", BRIGHT_CYAN = "\033[1;36m", NORMAL_CROSSED = "\033[0;9;37m", RED_BACKGROUND = "\033[1;41m", NORMAL_FAINT = "\033[0;2m";
#define dbg(x) cerr << BRIGHT_CYAN << #x << COLOR_RESET << " = " << (x) << NORMAL_FAINT << " (L" << __LINE__ << ") " << __FILE__ << COLOR_RESET << endl
#define dbgif(cond, x) ((cond) ? cerr << BRIGHT_CYAN << #x << COLOR_RESET << " = " << (x) << NORMAL_FAINT << " (L" << __LINE__ << ") " << __FILE__ << COLOR_RESET << endl : cerr)
#else
#define dbg(x) (x)
#define dbgif(cond, x) 0
#endif

template <int md> struct ModInt {
#if __cplusplus >= 201402L
#define MDCONST constexpr
#else
#define MDCONST
#endif
    using lint = long long;
    MDCONST static int mod() { return md; }
    static int get_primitive_root() {
        static int primitive_root = 0;
        if (!primitive_root) {
            primitive_root = [&]() {
                std::set<int> fac;
                int v = md - 1;
                for (lint i = 2; i * i <= v; i++)
                    while (v % i == 0) fac.insert(i), v /= i;
                if (v > 1) fac.insert(v);
                for (int g = 1; g < md; g++) {
                    bool ok = true;
                    for (auto i : fac)
                        if (ModInt(g).pow((md - 1) / i) == 1) {
                            ok = false;
                            break;
                        }
                    if (ok) return g;
                }
                return -1;
            }();
        }
        return primitive_root;
    }
    int val;
    MDCONST ModInt() : val(0) {}
    MDCONST ModInt &_setval(lint v) { return val = (v >= md ? v - md : v), *this; }
    MDCONST ModInt(lint v) { _setval(v % md + md); }
    MDCONST explicit operator bool() const { return val != 0; }
    MDCONST ModInt operator+(const ModInt &x) const { return ModInt()._setval((lint)val + x.val); }
    MDCONST ModInt operator-(const ModInt &x) const { return ModInt()._setval((lint)val - x.val + md); }
    MDCONST ModInt operator*(const ModInt &x) const { return ModInt()._setval((lint)val * x.val % md); }
    MDCONST ModInt operator/(const ModInt &x) const { return ModInt()._setval((lint)val * x.inv() % md); }
    MDCONST ModInt operator-() const { return ModInt()._setval(md - val); }
    MDCONST ModInt &operator+=(const ModInt &x) { return *this = *this + x; }
    MDCONST ModInt &operator-=(const ModInt &x) { return *this = *this - x; }
    MDCONST ModInt &operator*=(const ModInt &x) { return *this = *this * x; }
    MDCONST ModInt &operator/=(const ModInt &x) { return *this = *this / x; }
    friend MDCONST ModInt operator+(lint a, const ModInt &x) { return ModInt()._setval(a % md + x.val); }
    friend MDCONST ModInt operator-(lint a, const ModInt &x) { return ModInt()._setval(a % md - x.val + md); }
    friend MDCONST ModInt operator*(lint a, const ModInt &x) { return ModInt()._setval(a % md * x.val % md); }
    friend MDCONST ModInt operator/(lint a, const ModInt &x) {
        return ModInt()._setval(a % md * x.inv() % md);
    }
    MDCONST bool operator==(const ModInt &x) const { return val == x.val; }
    MDCONST bool operator!=(const ModInt &x) const { return val != x.val; }
    MDCONST bool operator<(const ModInt &x) const { return val < x.val; } // To use std::map<ModInt, T>
    friend std::istream &operator>>(std::istream &is, ModInt &x) {
        lint t;
        return is >> t, x = ModInt(t), is;
    }
    MDCONST friend std::ostream &operator<<(std::ostream &os, const ModInt &x) { return os << x.val; }
    MDCONST ModInt pow(lint n) const {
        ModInt ans = 1, tmp = *this;
        while (n) {
            if (n & 1) ans *= tmp;
            tmp *= tmp, n >>= 1;
        }
        return ans;
    }

    static std::vector<ModInt> facs, facinvs, invs;
    MDCONST static void _precalculation(int N) {
        int l0 = facs.size();
        if (N > md) N = md;
        if (N <= l0) return;
        facs.resize(N), facinvs.resize(N), invs.resize(N);
        for (int i = l0; i < N; i++) facs[i] = facs[i - 1] * i;
        facinvs[N - 1] = facs.back().pow(md - 2);
        for (int i = N - 2; i >= l0; i--) facinvs[i] = facinvs[i + 1] * (i + 1);
        for (int i = N - 1; i >= l0; i--) invs[i] = facinvs[i] * facs[i - 1];
    }
    MDCONST lint inv() const {
        if (this->val < std::min(md >> 1, 1 << 21)) {
            while (this->val >= int(facs.size())) _precalculation(facs.size() * 2);
            return invs[this->val].val;
        } else {
            return this->pow(md - 2).val;
        }
    }
    MDCONST ModInt fac() const {
        while (this->val >= int(facs.size())) _precalculation(facs.size() * 2);
        return facs[this->val];
    }
    MDCONST ModInt facinv() const {
        while (this->val >= int(facs.size())) _precalculation(facs.size() * 2);
        return facinvs[this->val];
    }
    MDCONST ModInt doublefac() const {
        lint k = (this->val + 1) / 2;
        return (this->val & 1) ? ModInt(k * 2).fac() / (ModInt(2).pow(k) * ModInt(k).fac())
                               : ModInt(k).fac() * ModInt(2).pow(k);
    }
    MDCONST ModInt nCr(const ModInt &r) const {
        return (this->val < r.val) ? 0 : this->fac() * (*this - r).facinv() * r.facinv();
    }
    MDCONST ModInt nPr(const ModInt &r) const {
        return (this->val < r.val) ? 0 : this->fac() * (*this - r).facinv();
    }

    ModInt sqrt() const {
        if (val == 0) return 0;
        if (md == 2) return val;
        if (pow((md - 1) / 2) != 1) return 0;
        ModInt b = 1;
        while (b.pow((md - 1) / 2) == 1) b += 1;
        int e = 0, m = md - 1;
        while (m % 2 == 0) m >>= 1, e++;
        ModInt x = pow((m - 1) / 2), y = (*this) * x * x;
        x *= (*this);
        ModInt z = b.pow(m);
        while (y != 1) {
            int j = 0;
            ModInt t = y;
            while (t != 1) j++, t *= t;
            z = z.pow(1LL << (e - j - 1));
            x *= z, z *= z, y *= z;
            e = j;
        }
        return ModInt(std::min(x.val, md - x.val));
    }
};
template <int md> std::vector<ModInt<md>> ModInt<md>::facs = {1};
template <int md> std::vector<ModInt<md>> ModInt<md>::facinvs = {1};
template <int md> std::vector<ModInt<md>> ModInt<md>::invs = {0};
using mint = ModInt<998244353>;

template <typename T> struct matrix {
    int H, W;
    std::vector<T> elem;
    typename std::vector<T>::iterator operator[](int i) { return elem.begin() + i * W; }
    inline T &at(int i, int j) { return elem[i * W + j]; }
    inline T get(int i, int j) const { return elem[i * W + j]; }
    int height() const { return H; }
    int width() const { return W; }
    std::vector<std::vector<T>> vecvec() const {
        std::vector<std::vector<T>> ret(H);
        for (int i = 0; i < H; i++) {
            std::copy(elem.begin() + i * W, elem.begin() + (i + 1) * W, std::back_inserter(ret[i]));
        }
        return ret;
    }
    operator std::vector<std::vector<T>>() const { return vecvec(); }
    matrix() = default;
    matrix(int H, int W) : H(H), W(W), elem(H * W) {}
    matrix(const std::vector<std::vector<T>> &d) : H(d.size()), W(d.size() ? d[0].size() : 0) {
        for (auto &raw : d) std::copy(raw.begin(), raw.end(), std::back_inserter(elem));
    }

    static matrix Identity(int N) {
        matrix ret(N, N);
        for (int i = 0; i < N; i++) ret.at(i, i) = 1;
        return ret;
    }

    matrix operator-() const {
        matrix ret(H, W);
        for (int i = 0; i < H * W; i++) ret.elem[i] = -elem[i];
        return ret;
    }
    matrix operator*(const T &v) const {
        matrix ret = *this;
        for (auto &x : ret.elem) x *= v;
        return ret;
    }
    matrix operator/(const T &v) const {
        matrix ret = *this;
        const T vinv = T(1) / v;
        for (auto &x : ret.elem) x *= vinv;
        return ret;
    }
    matrix operator+(const matrix &r) const {
        matrix ret = *this;
        for (int i = 0; i < H * W; i++) ret.elem[i] += r.elem[i];
        return ret;
    }
    matrix operator-(const matrix &r) const {
        matrix ret = *this;
        for (int i = 0; i < H * W; i++) ret.elem[i] -= r.elem[i];
        return ret;
    }
    matrix operator*(const matrix &r) const {
        matrix ret(H, r.W);
        for (int i = 0; i < H; i++) {
            for (int k = 0; k < W; k++) {
                for (int j = 0; j < r.W; j++) ret.at(i, j) += this->get(i, k) * r.get(k, j);
            }
        }
        return ret;
    }
    matrix &operator*=(const T &v) { return *this = *this * v; }
    matrix &operator/=(const T &v) { return *this = *this / v; }
    matrix &operator+=(const matrix &r) { return *this = *this + r; }
    matrix &operator-=(const matrix &r) { return *this = *this - r; }
    matrix &operator*=(const matrix &r) { return *this = *this * r; }
    bool operator==(const matrix &r) const { return H == r.H and W == r.W and elem == r.elem; }
    bool operator!=(const matrix &r) const { return H != r.H or W != r.W or elem != r.elem; }
    bool operator<(const matrix &r) const { return elem < r.elem; }
    matrix pow(int64_t n) const {
        matrix ret = Identity(H);
        bool ret_is_id = true;
        if (n == 0) return ret;
        for (int i = 63 - __builtin_clzll(n); i >= 0; i--) {
            if (!ret_is_id) ret *= ret;
            if ((n >> i) & 1) ret *= (*this), ret_is_id = false;
        }
        return ret;
    }
    std::vector<T> pow_vec(int64_t n, std::vector<T> vec) const {
        matrix x = *this;
        while (n) {
            if (n & 1) vec = x * vec;
            x *= x;
            n >>= 1;
        }
        return vec;
    };
    matrix transpose() const {
        matrix ret(W, H);
        for (int i = 0; i < H; i++) {
            for (int j = 0; j < W; j++) ret.at(j, i) = this->get(i, j);
        }
        return ret;
    }
    // Gauss-Jordan elimination
    // - Require inverse for every non-zero element
    // - Complexity: O(H^2 W)
    template <typename T2, typename std::enable_if<std::is_floating_point<T2>::value>::type * = nullptr>
    static int choose_pivot(const matrix<T2> &mtr, int h, int c) noexcept {
        int piv = -1;
        for (int j = h; j < mtr.H; j++) {
            if (mtr.get(j, c) and (piv < 0 or std::abs(mtr.get(j, c)) > std::abs(mtr.get(piv, c)))) piv = j;
        }
        return piv;
    }
    template <typename T2, typename std::enable_if<!std::is_floating_point<T2>::value>::type * = nullptr>
    static int choose_pivot(const matrix<T2> &mtr, int h, int c) noexcept {
        for (int j = h; j < mtr.H; j++) {
            if (mtr.get(j, c)) return j;
        }
        return -1;
    }
    matrix gauss_jordan() const {
        int c = 0;
        matrix mtr(*this);
        std::vector<int> ws;
        ws.reserve(W);
        for (int h = 0; h < H; h++) {
            if (c == W) break;
            int piv = choose_pivot(mtr, h, c);
            if (piv == -1) {
                c++;
                h--;
                continue;
            }
            if (h != piv) {
                for (int w = 0; w < W; w++) {
                    std::swap(mtr[piv][w], mtr[h][w]);
                    mtr.at(piv, w) *= -1; // To preserve sign of determinant
                }
            }
            ws.clear();
            for (int w = c; w < W; w++) {
                if (mtr.at(h, w) != 0) ws.emplace_back(w);
            }
            const T hcinv = T(1) / mtr.at(h, c);
            for (int hh = 0; hh < H; hh++)
                if (hh != h) {
                    const T coeff = mtr.at(hh, c) * hcinv;
                    for (auto w : ws) mtr.at(hh, w) -= mtr.at(h, w) * coeff;
                    mtr.at(hh, c) = 0;
                }
            c++;
        }
        return mtr;
    }
    int rank_of_gauss_jordan() const {
        for (int i = H * W - 1; i >= 0; i--) {
            if (elem[i]) return i / W + 1;
        }
        return 0;
    }
    T determinant_of_upper_triangle() const {
        T ret = 1;
        for (int i = 0; i < H; i++) ret *= get(i, i);
        return ret;
    }
    int inverse() {
        assert(H == W);
        std::vector<std::vector<T>> ret = Identity(H), tmp = *this;
        int rank = 0;
        for (int i = 0; i < H; i++) {
            int ti = i;
            while (ti < H and tmp[ti][i] == 0) ti++;
            if (ti == H) {
                continue;
            } else {
                rank++;
            }
            ret[i].swap(ret[ti]), tmp[i].swap(tmp[ti]);
            T inv = T(1) / tmp[i][i];
            for (int j = 0; j < W; j++) ret[i][j] *= inv;
            for (int j = i + 1; j < W; j++) tmp[i][j] *= inv;
            for (int h = 0; h < H; h++) {
                if (i == h) continue;
                const T c = -tmp[h][i];
                for (int j = 0; j < W; j++) ret[h][j] += ret[i][j] * c;
                for (int j = i + 1; j < W; j++) tmp[h][j] += tmp[i][j] * c;
            }
        }
        *this = ret;
        return rank;
    }
    friend std::vector<T> operator*(const matrix &m, const std::vector<T> &v) {
        assert(m.W == int(v.size()));
        std::vector<T> ret(m.H);
        for (int i = 0; i < m.H; i++) {
            for (int j = 0; j < m.W; j++) ret[i] += m.get(i, j) * v[j];
        }
        return ret;
    }
    friend std::vector<T> operator*(const std::vector<T> &v, const matrix &m) {
        assert(int(v.size()) == m.H);
        std::vector<T> ret(m.W);
        for (int i = 0; i < m.H; i++) {
            for (int j = 0; j < m.W; j++) ret[j] += v[i] * m.get(i, j);
        }
        return ret;
    }
    std::vector<T> prod(const std::vector<T> &v) const { return (*this) * v; }
    std::vector<T> prod_left(const std::vector<T> &v) const { return v * (*this); }
    friend std::ostream &operator<<(std::ostream &os, const matrix &x) {
        os << "[(" << x.H << " * " << x.W << " matrix)";
        os << "\n[column sums: ";
        for (int j = 0; j < x.W; j++) {
            T s = 0;
            for (int i = 0; i < x.H; i++) s += x.get(i, j);
            os << s << ",";
        }
        os << "]";
        for (int i = 0; i < x.H; i++) {
            os << "\n[";
            for (int j = 0; j < x.W; j++) os << x.get(i, j) << ",";
            os << "]";
        }
        os << "]\n";
        return os;
    }
    friend std::istream &operator>>(std::istream &is, matrix &x) {
        for (auto &v : x.elem) is >> v;
        return is;
    }
};

int main() {
    int N, M;
    cin >> N >> M;
    vector<int> to(N);
    REP(e, M) {
        int a, b;
        cin >> a >> b;
        a--;
        b--;
        to[a] |= 1 << b, to[b] |= 1 << a;
    }
    vector<int> ne(1 << N);

    vector<mint> pow2(1000, 1);
    FOR(i, 1, pow2.size()) pow2[i] = pow2[i - 1] * 2;
    FOR(s, 1, 1 << N) {
        int cnt = 0;
        int v = __builtin_popcount(s);
        REP(i, N) {
            if ((s >> i) & 1) {
                cnt += __builtin_popcount(to[i] & s);
            }
        }
        ne[s] = cnt / 2;
    }
    vector<mint> dp(1 << N);
    // REP(i, N) dp[1 << i] = 1;
    for (int s = 1; s < 1 << N; s++) {
        // int h = __lg(s);
        // int ss = s - (1 << h);
        // for (int t = ss; t; t = (t - 1) & ss) {
        //     int u = s - t;
        //     // int e = ne[s] - ne[t] - ne[u];
        //     dp[s] += dp[t] * dp[u] * __builtin_popcount(to[h] & t);
        // }
        vector<int> idx;
        REP(i, N) if ((s >> i) & 1) idx.push_back(i);
        const int V = idx.size();
        matrix<mint> M(V - 1, V - 1);
        REP(i, idx.size() - 1) {
            int u = idx[i];
            REP(j, idx.size()) {
                int v = idx[j];
                if ((to[u] >> v) & 1) {
                    M[i][i] += 1;
                    if (j + 1 < V) M[i][j] -= 1;
                }
            }
        }
        dp[s] = M.gauss_jordan().determinant_of_upper_triangle();
    }
    dbg(dp);

    mint ret = 0;
    FOR(s, 1, dp.size()) {
        for (int t = (s - 1) & s; t; t = (t - 1) & s) {
            int u = (1 << N) - 1 - s;
            int v = s - t;
            if (!t or !u or !v) continue;
            int eut = ne[t | u] - ne[t] - ne[u], euv = ne[u | v] - ne[u] - ne[v];
            ret += dp[t] * dp[u] * dp[v] * (pow2[eut] - 1) * (pow2[euv] - 1);
        }
    }
    cout << ret << '\n';
}
accuracy: 1
time_ms: 1120
memory_kb: 32884
final_rank: 17

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

#define REP(i,n) for(int i=0;i<(n);i++)

//BEGIN CUT HERE
template<typename T, T MOD = 998244353>
struct Mint{
  inline static constexpr T mod = MOD;
  T v;
  Mint():v(0){}
  Mint(signed v):v(v){}
  Mint(long long t){v=t%MOD;if(v<0) v+=MOD;}

  Mint pow(long long k){
    Mint res(1),tmp(v);
    while(k){
      if(k&1) res*=tmp;
      tmp*=tmp;
      k>>=1;
    }
    return res;
  }

  static Mint add_identity(){return Mint(0);}
  static Mint mul_identity(){return Mint(1);}

  Mint inv(){return pow(MOD-2);}

  Mint& operator+=(Mint a){v+=a.v;if(v>=MOD)v-=MOD;return *this;}
  Mint& operator-=(Mint a){v+=MOD-a.v;if(v>=MOD)v-=MOD;return *this;}
  Mint& operator*=(Mint a){v=1LL*v*a.v%MOD;return *this;}
  Mint& operator/=(Mint a){return (*this)*=a.inv();}

  Mint operator+(Mint a) const{return Mint(v)+=a;}
  Mint operator-(Mint a) const{return Mint(v)-=a;}
  Mint operator*(Mint a) const{return Mint(v)*=a;}
  Mint operator/(Mint a) const{return Mint(v)/=a;}

  Mint operator+() const{return *this;}
  Mint operator-() const{return v?Mint(MOD-v):Mint(v);}

  bool operator==(const Mint a)const{return v==a.v;}
  bool operator!=(const Mint a)const{return v!=a.v;}

  static Mint comb(long long n,int k){
    Mint num(1),dom(1);
    for(int i=0;i<k;i++){
      num*=Mint(n-i);
      dom*=Mint(i+1);
    }
    return num/dom;
  }
};
template<typename T, T MOD>
ostream& operator<<(ostream &os,Mint<T, MOD> m){os<<m.v;return os;}
//END CUT HERE

using mint=Mint<long long>;

int g[17][17];//隣接グラフ
int edge[1<<17];//edge[S]:S内の辺の本数
mint a[1<<17];//a[S]:Sの全域木の個数
mint two_pow[17*17*2];

mint TreeQuantity(vector<vector<mint>> &v){//行列木定理
  int n=v.size();
  n--;
  mint res=1;
  REP(i,n){
    for(int j=i;j<n;j++)
      if(v[j][i].v!=0){
        for(int k=i;k<n;k++)swap(v[i][k],v[j][k]);
        j=n;
      }
    if(v[i][i].v==0)return 0;
    res*=v[i][i];
    mint inv=mint(1)/v[i][i];
    for(int j=i+1;j<n;j++)
      for(int k=n-1;k>=i;k--)
        v[j][k]-=v[i][k]*v[j][i]*inv;
  }
  return res;
}

int main(){
  two_pow[0]=1;
  for(int i=1;i<17*17*2;i++)two_pow[i]=two_pow[i-1]*2;

  int n,m;cin>>n>>m;
  REP(i,m){
    int u,v;cin>>u>>v;u--;v--;
    if(u==v)continue;
    g[u][v]=g[v][u]=1;
  }

  REP(S,1<<n){
    vector<int> w;//Sの要素
    REP(i,n)if((S>>i)&1)w.push_back(i);

    vector v(w.size(),vector<mint>(w.size(),0));//TreeQuantityに渡す行列
    REP(i,w.size())REP(j,w.size()){
      v[i][j]=-g[w[i]][w[j]];
      if(i<j)edge[S]+=g[w[i]][w[j]];
    }
    if(w.size()>=n-1)continue;
    vector<int> jisuu(w.size(),0);
    REP(i,w.size())REP(j,w.size())jisuu[i]+=g[w[i]][w[j]];
    REP(i,w.size())v[i][i]=jisuu[i];
    a[S]=TreeQuantity(v);
  }

  int ALL=(1<<n)-1;
  mint ans=0;
  REP(S,ALL)for(int T=(S-1)&S;T;T=(T-1)&S)
    ans+=a[S^T]*a[T]*a[ALL^S]
      *(two_pow[ edge[S]-edge[S^T]-edge[T] ]-1)*(two_pow[ edge[ALL^T]-edge[S^T]-edge[ALL^S] ]-1);
  cout<<ans.v<<endl;
}
accuracy: 1
time_ms: 1390
memory_kb: 4972
final_rank: 8

code_snippet: #include <bits/stdc++.h>
using namespace std;
const long long MOD = 998244353;
long long modpow(long long a, long long b){
	long long ans = 1;
	while (b > 0){
		if (b % 2 == 1){
			ans *= a;
			ans %= MOD;
		}
		a *= a;
		a %= MOD;
		b /= 2;
	}
	return ans;
}
long long modinv(long long a){
	return modpow(a, MOD - 2);
}
long long det(vector<vector<long long>> &S){
  int N = S.size();
  long long ans = 1;
  for (int i = 0; i < N; i++){
    int p = i;
    for (int j = i + 1; j < N; j++){
      if (S[j][i] > 0){
        p = j;
      }
    }
    swap(S[i], S[p]);
    if (p != i){
      ans = (MOD - ans) % MOD;
    }
    if (S[i][i] == 0){
      return 0;
    }
    ans *= S[i][i];
    ans %= MOD;
    long long c = modinv(S[i][i]);
    for (int j = i + 1; j < N; j++){
      S[i][j] *= c;
      S[i][j] %= MOD;
    }
    for (int j = i + 1; j < N; j++){
      for (int k = i + 1; k < N; k++){
        S[j][k] += MOD - S[j][i] * S[i][k] % MOD;
        S[j][k] %= MOD;
      }
    }
  }
  return ans;
}
int main(){
  int N, M;
  cin >> N >> M;
  vector<vector<bool>> E(N, vector<bool>(N, false));
  for (int i = 0; i < M; i++){
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    assert(a != b);
    if (a != b){
      E[a][b] = true;
      E[b][a] = true;
    }
  }
  vector<long long> S(1 << N, 0);
  for (int i = 1; i < (1 << N); i++){
    vector<int> id;
    for (int j = 0; j < N; j++){
      if ((i >> j & 1) == 1){
        id.push_back(j);
      }
    }
    int cnt = id.size();
    vector<vector<long long>> M(cnt, vector<long long>(cnt, 0));
    for (int j = 0; j < cnt; j++){
      for (int k = j + 1; k < cnt; k++){
        int u = id[j];
        int v = id[k];
        if (E[u][v]){
          M[j][j]++;
          M[k][k]++;
          M[j][k]--;
          M[k][j]--;
        }
      }
    }
    vector<vector<long long>> A(cnt - 1, vector<long long>(cnt - 1, 0));
    for (int j = 0; j < cnt - 1; j++){
      for (int k = 0; k < cnt - 1; k++){
        A[j][k] = (M[j + 1][k + 1] + MOD) % MOD;
      }
    }
    S[i] = det(A);
  }
  vector<long long> C(M + 1, 0);
  for (int i = 1; i <= M; i++){
    C[i] = 1;
    for (int j = 0; j < i; j++){
      C[i] *= 2;
      C[i] %= MOD;
    }
    C[i]--;
  }
  vector<vector<int>> su(1 << N);
  for (int i = 0; i < (1 << N); i++){
    for (int j = 0; j < N; j++){
      if ((i >> j & 1) == 1){
        su[i].push_back(j);
      }
    }
  }
  vector<int> be(N, 0);
  for (int i = 0; i < N; i++){
    for (int j = 0; j < N; j++){
      if (E[i][j]){
        be[i] |= 1 << j;
      }
    }
  }
  vector<int> pc(1 << N);
  for (int i = 0; i < (1 << N); i++){
    pc[i] = __builtin_popcount(i);
  }
  long long ans = 0;
  for (int i = 1; i < (1 << N); i++){
    int s = (1 << N) - 1 - i;
    long long add = 0;
    for (int j = s; j >= 0; j = (j - 1) & s){
      if (j != s){
        int b = j, c = s - j;
        if (b < c){
          break;
        }
        long long t = S[b] * S[c] % MOD;
        int p = 0, q = 0;
        for (int k : su[i]){
          p += pc[be[k] & b];
          q += pc[be[k] & c];
        }
        t *= C[p];
        t %= MOD;
        t *= C[q];
        t %= MOD;
        add += t;
      }
      if (j == 0){
        break;
      }
    }
    add %= MOD;
    add *= S[i];
    add %= MOD;
    ans += add;
    ans %= MOD;
  }
  ans *= 2;
  ans %= MOD;
  cout << ans << endl;
}
accuracy: 1
time_ms: 760
memory_kb: 15968
final_rank: 9

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <cstdio>
#include <ctime>
#include <assert.h>
#include <chrono>
#include <random>
#include <numeric>
#include <set>
#include <deque>
#include <stack>
#include <sstream>
#include <utility>
#include <cstring>
#include <unordered_map>
#include <unordered_set>
#include <tuple>
using namespace std;
#define siz(v) (ll)(v).size()
#define rep(i, a, n) for (ll i = a; i < (ll)(n); ++i)
typedef long long int ll;
typedef unsigned long long ull;

mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
ll myRand(ll B) {
    return (ull)rng() % B;
}

constexpr ll mod = 998244353;
template <typename T> using V = vector<T>;
template <typename T> using VV = vector<vector<T>>;
template <const long long MOD = mod> // MODを適宜設定すること
struct Mint {
  using M = Mint;
  ll a;
  Mint(ll x = 0) {
    a = x % MOD;
    if (a < 0)
      a += MOD;
  } // Mintで

  M pow(ll n) {
    assert(n >= 0);
    M b = *this, r = 1 % MOD;
    while (n) {
      if (n & 1)
        r *= b;
      b *= b;
      n >>= 1;
    }
    return r;
  }

  M inv() { return pow(MOD - 2); }

  M &operator+=(M r) {
    a += r.a;
    if (a >= MOD)
      a -= MOD;
    return *this;
  }
  M &operator-=(M r) {
    a += MOD - r.a;
    if (a >= MOD)
      a -= MOD;
    return *this;
  }
  M &operator*=(M r) {
    a = 1LL * a * r.a % MOD;
    return *this;
  }
  M &operator/=(M r) { return (*this) *= r.inv(); }
  M operator+(M r) const { return M(a) += r; };
  M operator-(M r) const { return M(a) -= r; };
  M operator*(M r) const { return M(a) *= r; };
  M operator/(M r) const { return M(a) /= r; };
  M operator-() const { return M() - *this; }

  friend ostream &operator<<(ostream &os, const M &r) { return os << r.a; }
};
using M = Mint<mod>;

M numST(VV<M> a) {
  int n = siz(a);

  rep(i, 0, n) {
    int cnt = 0;
    rep(j, i + 1, n) {
      a[i][j] *= -1;
      a[j][i] *= -1;
    }

    rep(j, 0, n) if (i != j && a[i][j].a != 0)++cnt;
    a[i][i] = cnt;
  }
  a.pop_back();
  rep(i, 0, n - 1) a[i].pop_back();
  --n;

  M ans = 1;
  rep(i, 0, n) {
    rep(j, i + 1, n) {
      while (a[j][i].a != 0) { // gcd step
        M t = a[i][i] / a[j][i];
        if (t.a)
          rep(k, i, n) a[i][k] = (a[i][k] - a[j][k] * t);
        swap(a[i], a[j]);
        ans *= -1;
      }
    }
    ans = ans * a[i][i];
    if (!ans.a)
      return 0;
  }
  return (ans + mod);
}

struct UnionFind{
    vector<int> par,num;
    UnionFind(int n):par(n),num(n,1){
        iota(par.begin(),par.end(),0);  //include<numeric>
    }
    int find(int v){
        return (par[v]==v)?v:(par[v]=find(par[v]));
    }
    void unite(int u,int v){
        u=find(u),v=find(v);
        if(u==v)return;
        if(num[u]<num[v])swap(u,v);
        num[u]+=num[v];
        par[v]=u;
    }
    bool same(int u,int v){
        return find(u) == find(v);
    }
    int size(int v){
        return num[find(v)];
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    int n,m; cin >> n >> m;
    vector<pair<int,int>> e(m);
    // 隣接行列
    vector<vector<int>> G(n,vector<int>(n));
    for(int i=0;i<m;i++){
        cin >> e[i].first >> e[i].second;
        e[i].first--;
        e[i].second--;
        G[e[i].first][e[i].second] = G[e[i].second][e[i].first] = 1;
    }
    {
        UnionFind uf(n);
        for(int i=0;i<m;i++){
            uf.unite(e[i].first,e[i].second);
        }
        if(uf.size(0) != n) {
            cout << 0 << endl;
            return 0;
        }
    }
    // 連結かどうか
    vector<int> ok(1<<n);
    // 辺の本数
    vector<int> cnt(1<<n);
    // 同一の市内において木にする方法
    vector<M> val(1<<n);

    vector<vector<M>> g(n,vector<M>(n));
    for(int i=1;i<(1<<n);i++){
        UnionFind uf(n);
        int s; // どこか集合内の点を任意に一つ取る
        for(int j=0;j<n;j++){
            for(int k=0;k<n;k++){
                g[j][k] = 0;
            }
        }
        for(int j=0;j<n;j++){
            if ((1<<j)&i) {
                s = j;
            }
            else continue;;
            for(int k=j+1;k<n;k++){
                if ((1<<k)&i and G[j][k]) {
                    cnt[i]++;
                    g[j][k] = g[k][j] = 1;
                    uf.unite(j, k);
                }
            }
        }
        for(int j=0;j<n;j++){
            if((1<<j)&i)continue;
            g[s][j] = g[j][s] = 1;
        }
        if(uf.size(s) != __builtin_popcount(i)) {
            ok[i] = 0;
            continue;
        }
        else{
            ok[i] = 1;
        }
        val[i] = numST(g);
        // cout << i << " " << ok[i] << " " << cnt[i] << " " << val[i] << endl;
    }
    M res = 0;
    vector<M> po(m+1);
    for(int i=0;i<=m;i++){
        po[i] = M(2).pow(i);
    }
    auto dfs=[&](auto self,int N,int B,int C,int D)->void{
        if(N == n){
            if(B == 0 or C == 0 or D == 0)return;
            if(!ok[B] or !ok[C] or !ok[D])return;
            M add = val[B]*val[C]*val[D]; // 全域木を作る総数
            ll cnt1 = cnt[B+D]-cnt[B]-cnt[D];
            ll cnt2 = cnt[D+C]-cnt[D]-cnt[C];
            add *= (po[cnt1]-1);
            add *= (po[cnt2]-1);
            res += add;
            return;
        }
        self(self,N+1,B+(1<<N),C,D);
        self(self,N+1,B,C+(1<<N),D);
        self(self,N+1,B,C,D+(1<<N));
    };
    dfs(dfs,0,0,0,0);
    cout << res << endl;
}
accuracy: 1
time_ms: 2750
memory_kb: 5188
final_rank: 18

code_snippet: #include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
template<typename T1,typename T2> bool chmin(T1 &a,T2 b){if(a<=b)return 0; a=b; return 1;}
template<typename T1,typename T2> bool chmax(T1 &a,T2 b){if(a>=b)return 0; a=b; return 1;}
int dx[4]={0,1,0,-1}, dy[4]={1,0,-1,0};
long double eps = 1e-9;
long double pi = acos(-1);

// ushi san no modint desu, tabun
template< int mod = 1000000007 >
struct ModInt {
	int x;
	ModInt() : x(0) {}
	ModInt(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}
	ModInt &operator+=(const ModInt &p) {
		if((x += p.x) >= mod) x -= mod;
		return *this;
	}
	ModInt &operator-=(const ModInt &p) {
		if((x += mod - p.x) >= mod) x -= mod;
		return *this;
	}
	ModInt &operator*=(const ModInt &p) {
		x = (int) (1LL * x * p.x % mod);
		return *this;
	}
	ModInt &operator/=(const ModInt &p) {
		*this *= p.inverse();
		return *this;
	}

	ModInt operator-() const { return ModInt(-x); }
	ModInt operator+(const ModInt &p) const { return ModInt(*this) += p; }
	ModInt operator-(const ModInt &p) const { return ModInt(*this) -= p; }
	ModInt operator*(const ModInt &p) const { return ModInt(*this) *= p; }
	ModInt operator/(const ModInt &p) const { return ModInt(*this) /= p; }
	bool operator==(const ModInt &p) const { return x == p.x; }
	bool operator!=(const ModInt &p) const { return x != p.x; }

	ModInt inverse() const {
		int a = x, b = mod, u = 1, v = 0, t;
		while(b > 0) {
			t = a / b;
			swap(a -= t * b, b);
			swap(u -= t * v, v);
		}
		return ModInt(u);
	}

	ModInt pow(int64_t n) const {
		ModInt ret(1), mul(x);
		while(n > 0) {
			if(n & 1) ret *= mul;
			mul *= mul;
			n >>= 1;
		}
		return ret;
	}

	friend ostream &operator<<(ostream &os, const ModInt &p) {
		return os << p.x;
	}

	friend istream &operator>>(istream &is, ModInt &a) {
		int64_t t;
		is >> t;
		a = ModInt< mod >(t);
		return (is);
	}

	static int get_mod() { return mod; }
};

// const int mod = 1000000007;
const int mod = 998244353;
using mint = ModInt< mod >;

// arigatou ushi san ...
template< class T >
struct Matrix {
  vector< vector< T > > A;

  Matrix() {}

  Matrix(size_t n, size_t m) : A(n, vector< T >(m, 0)) {}

  Matrix(size_t n) : A(n, vector< T >(n, 0)) {};

  size_t height() const {
    return (A.size());
  }

  size_t width() const {
    return (A[0].size());
  }

  inline const vector< T > &operator[](int k) const {
    return (A.at(k));
  }

  inline vector< T > &operator[](int k) {
    return (A.at(k));
  }

  static Matrix I(size_t n) {
    Matrix mat(n);
    for(int i = 0; i < n; i++) mat[i][i] = 1;
    return (mat);
  }

  Matrix &operator+=(const Matrix &B) {
    size_t n = height(), m = width();
    assert(n == B.height() && m == B.width());
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        (*this)[i][j] += B[i][j];
    return (*this);
  }

  Matrix &operator-=(const Matrix &B) {
    size_t n = height(), m = width();
    assert(n == B.height() && m == B.width());
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        (*this)[i][j] -= B[i][j];
    return (*this);
  }

  Matrix &operator*=(const Matrix &B) {
    size_t n = height(), m = B.width(), p = width();
    assert(p == B.height());
    vector< vector< T > > C(n, vector< T >(m, 0));
    for(int i = 0; i < n; i++)
      for(int j = 0; j < m; j++)
        for(int k = 0; k < p; k++)
          C[i][j] = (C[i][j] + (*this)[i][k] * B[k][j]);
    A.swap(C);
    return (*this);
  }

  Matrix &operator^=(long long k) {
    Matrix B = Matrix::I(height());
    while(k > 0) {
      if(k & 1) B *= *this;
      *this *= *this;
      k >>= 1LL;
    }
    A.swap(B.A);
    return (*this);
  }

  Matrix operator+(const Matrix &B) const {
    return (Matrix(*this) += B);
  }

  Matrix operator-(const Matrix &B) const {
    return (Matrix(*this) -= B);
  }

  Matrix operator*(const Matrix &B) const {
    return (Matrix(*this) *= B);
  }

  Matrix operator^(const long long k) const {
    return (Matrix(*this) ^= k);
  }

  friend ostream &operator<<(ostream &os, Matrix &p) {
    size_t n = p.height(), m = p.width();
    for(int i = 0; i < n; i++) {
      os << "[";
      for(int j = 0; j < m; j++) {
        os << p[i][j] << (j + 1 == m ? "]\n" : ",");
      }
    }
    return (os);
  }


  T determinant() {
    Matrix B(*this);
    assert(width() == height());
    T ret = 1;
    for(int i = 0; i < width(); i++) {
      int idx = -1;
      for(int j = i; j < width(); j++) {
        if(B[j][i] != 0) idx = j;
      }
      if(idx == -1) return (0);
      if(i != idx) {
        ret *= -1;
        swap(B[i], B[idx]);
      }
      ret *= B[i][i];
      T vv = B[i][i];
      for(int j = 0; j < width(); j++) {
        B[i][j] /= vv;
      }
      for(int j = i + 1; j < width(); j++) {
        T a = B[j][i];
        for(int k = 0; k < width(); k++) {
          B[j][k] -= B[i][k] * a;
        }
      }
    }
    return (ret);
  }
};

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout << fixed << setprecision(20);

	// O(2^N*N^3)で行列木定理
	// O(3^N)で数え上げ
	int n,m;
	cin>>n>>m;
	vector<vector<int>> v(n);
	static bool e[17][17];
	for(int i=0;i<m;i++){
		int a,b;
		cin>>a>>b;
		a--,b--;
		e[a][b]=1;
		e[b][a]=1;
		v[a].push_back(b);
		v[b].push_back(a);
	}
	static mint cnt[1<<17];
	for(int bit=1;bit<(1<<n);bit++){
		vector<int> t;
		for(int i=0;i<n;i++)if(bit&(1<<i))t.push_back(i);
		int m = t.size();
		if(m == 1){
			cnt[bit] = 1;
			continue;
		}
		Matrix<mint> a(m-1,m-1);
		for(int i=0;i<m;i++){
			for(int j=i+1;j<m;j++){
				if(e[t[i]][t[j]] == false)continue;
				if(i<m-1) a[i][i] += 1;
				if(j<m-1) a[j][j] += 1;
				if(i<m-1 and j<m-1){
					a[i][j] -= 1;
					a[j][i] -= 1;
				}
			}
		}
		cnt[bit] = a.determinant();
	}

	static int edge[1<<17][17];
	static int sum[1<<17];
	static int msb[1<<17];
	for(int bit=0;bit<(1<<n);bit++){
		for(int i=0;i<n;i++){
			if(bit & (1<<i)){
				msb[bit] = i;
				for(auto j:v[i]){
					if(bit & (1<<j))continue;
					edge[bit][j] += 1;
					sum[bit] += 1;
				}
			}
		}
	}

	static mint bi[17*17+5];
	bi[0] = 1;
	for(int i=0;i<n*n+4;i++){
		bi[i+1] = bi[i] * 2;
		bi[i]-=1;
	}
	mint ans = 0;
	static int now[1<<17];
	for(int D=1;D<(1<<n);D++){
		int BC = (1<<n) - 1 - D;
		now[0] = 0;
		for(int B=(1<<n)-1;B>=0;B--){
			B &= BC;
			int C = BC-B;
			if(C == 0 || B == 0)continue;
			now[C] = now[C ^ (1<<msb[C])] + edge[D][msb[C]];
			// assert(C > (C^(1<<msb[C])));
			mint ret = cnt[C] * cnt[B] * cnt[D] * bi[now[C]] * bi[sum[D] - now[C]];
			// assert(sum[D]-now[C]>=0);
			ans += ret;
		}
	}
	cout << ans << endl;
}
accuracy: 1
time_ms: 1260
memory_kb: 14148
final_rank: 11

code_snippet: //*
#pragma GCC target("avx2")
#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")
//*/

#include <bits/stdc++.h>

// #include <atcoder/all>
// #include <atcoder/modint>

using namespace std;
// using namespace atcoder;

#define DEBUG(x) cerr << #x << ": " << x << endl;
#define DEBUG_VEC(v)                                        \
    cerr << #v << ":";                                      \
    for (int iiiiiiii = 0; iiiiiiii < v.size(); iiiiiiii++) \
        cerr << " " << v[iiiiiiii];                         \
    cerr << endl;
#define DEBUG_MAT(v)                            \
    cerr << #v << endl;                         \
    for (int i = 0; i < v.size(); i++) {        \
        for (int j = 0; j < v[i].size(); j++) { \
            cerr << v[i][j] << " ";             \
        }                                       \
        cerr << endl;                           \
    }
typedef long long ll;
// #define int ll

#define vi vector<int>
#define vl vector<ll>
#define vii vector<vector<int>>
#define vll vector<vector<ll>>
#define vs vector<string>
#define pii pair<int, int>
#define pis pair<int, string>
#define psi pair<string, int>
#define pll pair<ll, ll>
template <class S, class T>
pair<S, T> operator+(const pair<S, T> &s, const pair<S, T> &t) {
    return pair<S, T>(s.first + t.first, s.second + t.second);
}
template <class S, class T>
pair<S, T> operator-(const pair<S, T> &s, const pair<S, T> &t) { return pair<S, T>(s.first - t.first, s.second - t.second); }
template <class S, class T>
ostream &operator<<(ostream &os, pair<S, T> p) {
    os << "(" << p.first << ", " << p.second << ")";
    return os;
}
#define X first
#define Y second
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
#define rep1(i, n) for (int i = 1; i <= (int)(n); i++)
#define rrep(i, n) for (int i = (int)(n)-1; i >= 0; i--)
#define rrep1(i, n) for (int i = (int)(n); i > 0; i--)
#define REP(i, a, b) for (int i = a; i < b; i++)
#define in(x, a, b) (a <= x && x < b)
#define all(c) c.begin(), c.end()
void YES(bool t = true) {
    cout << (t ? "YES" : "NO") << endl;
}
void Yes(bool t = true) { cout << (t ? "Yes" : "No") << endl; }
void yes(bool t = true) { cout << (t ? "yes" : "no") << endl; }
void NO(bool t = true) { cout << (t ? "NO" : "YES") << endl; }
void No(bool t = true) { cout << (t ? "No" : "Yes") << endl; }
void no(bool t = true) { cout << (t ? "no" : "yes") << endl; }
template <class T>
bool chmax(T &a, const T &b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <class T>
bool chmin(T &a, const T &b) {
    if (a > b) {
        a = b;
        return 1;
    }
    return 0;
}
#define UNIQUE(v) v.erase(std::unique(v.begin(), v.end()), v.end());
const ll inf = 1000000001;
const ll INF = (ll)1e18 + 1;
const long double pi = 3.1415926535897932384626433832795028841971L;
int popcount(ll t) { return __builtin_popcountll(t); }
// int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
// int dx2[8] = { 1,1,0,-1,-1,-1,0,1 }, dy2[8] = { 0,1,1,1,0,-1,-1,-1 };
vi dx = {0, 1, 0, -1}, dy = {-1, 0, 1, 0};
// vi dx2 = { 1,1,0,-1,-1,-1,0,1 }, dy2 = { 0,1,1,1,0,-1,-1,-1 };
struct Setup_io {
    Setup_io() {
        ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
        cout << fixed << setprecision(25);
    }
} setup_io;
// const ll MOD = 1000000007;
const ll MOD = 998244353;
// #define mp make_pair
//#define endl '\n'

const int MAXN = 555555;

vl fact(MAXN);
vl rfact(MAXN);

ll mod_pow(ll x, ll p, ll M = MOD) {
    if (p < 0) {
        x = mod_pow(x, M - 2, M);
        p = -p;
    }
    x %= M;
    ll a = 1;
    while (p) {
        if (p % 2)
            a = a * x % M;
        x = x * x % M;
        p /= 2;
    }
    return a;
}

ll mod_inverse(ll a, ll M = MOD) {
    return mod_pow(a, M - 2, M);
}

void set_fact(ll n, ll M = MOD) {
    fact[0] = 1;
    for (ll i = 1; i <= n; i++) {
        fact[i] = i * fact[i - 1] % M;
    }
    rfact[n] = mod_inverse(fact[n], M);
    for (ll i = n - 1; i >= 0; i--) {
        rfact[i] = (i + 1) * rfact[i + 1] % M;
    }
}

//http://drken1215.hatenablog.com/entry/2018/06/08/210000
ll nCr(ll n, ll r, ll M = MOD) {
    if (r > n) return 0;
    assert(fact[2] == 2);
    ll ret = fact[n];
    if (rfact[r] == 0) {
        rfact[r] = mod_inverse(fact[r], M);
    }
    ret = (ret * rfact[r]) % M;
    if (rfact[n - r] == 0) {
        rfact[n - r] = mod_inverse(fact[n - r], M);
    }
    ret = (ret * rfact[n - r]) % M;
    return ret;
}

ll nHr(ll n, ll r) {
    return nCr(n + r - 1, r);
}

class UnionFind {
  public:
    int n;
    vi par;
    vi ran;
    vi num;
    int g;

    UnionFind(int _n) {
        n = _n;
        g = n;
        par.resize(n);
        ran.resize(n);
        num.resize(n);
        for (int i = 0; i < n; i++) {
            par[i] = i;
            ran[i] = 0;
            num[i] = 1;
        }
    }

    int find(int x) {
        if (par[x] == x) {
            return x;
        } else {
            return par[x] = find(par[x]);
        }
    }

    void unite(int x, int y) {
        x = find(x);
        y = find(y);
        int numsum = num[x] + num[y];
        if (x == y) {
            return;
        }
        if (ran[x] < ran[y]) {
            par[x] = y;
        } else {
            par[y] = x;
            if (ran[x] == ran[y]) {
                ran[x]++;
            }
        }
        num[x] = num[y] = numsum;
        g--;
    }

    bool same(int x, int y) {
        return find(x) == find(y);
    }
};

class Zaatsu {
    bool is_build;
    vector<ll> elements;

  public:
    Zaatsu() : is_build(false) {}

    void add(ll x) {
        elements.push_back(x);
    }

    void add(vl x) {
        for (int i = 0; i < (int)x.size(); i++) {
            elements.push_back(x[i]);
        }
    }

    int build() {
        sort(elements.begin(), elements.end());
        UNIQUE(elements);
        is_build = true;
        return elements.size();
    }

    int operator[](ll x) {
        assert(is_build);
        return lower_bound(elements.begin(), elements.end(), x) - elements.begin();
    }

    ll original(int i) {
        assert(is_build);
        return elements[i];
    }
};

const int N = 20;
vii G(N);
int n, m;

vector<int> top(1 << N);

bool contain(int mask, int i) {
    return mask & (1 << i);
}

// 集合 x と集合 a の間の辺の数を計算
ll calc_edge_num(int x, int a, vi &e_num, vi &filled) {
    if (e_num[x] != -1) return e_num[x];

    // 集合 x から頂点 u を除いた集合 px と a の間の辺の数を計算
    int u = top[x];
    int px = x - (1 << u);
    int res = calc_edge_num(px, a, e_num, filled);
    assert(res != -1);

    // 頂点 u と集合 a の間の辺の数を計算
    for (int v : G[u]) {
        if (contain(a, v)) res++;
    }
    e_num[x] = res;
    filled.push_back(x);
    return res;
}

// using mint = modint998244353;

ll det(vll G) {
    // DEBUG_MAT(G);
    int n = G.size();
    ll ans = 1;
    rep(j, n) {
        int i = -1;
        REP(k, j, n) {
            if (G[k][j] != 0) {
                i = k;
                break;
            }
        }
        if (i == -1) return 0;

        if (i != j) {
            swap(G[i], G[j]);
        }

        (ans *= G[j][j]) %= MOD;
        ll inv = mod_inverse(G[j][j]);
        rep(jj, n) {
            (G[j][jj] *= inv) %= MOD;
        }

        rep(i, n) {
            if (i == j) continue;
            ll mul = G[i][j];
            rep(k, n) {
                (G[i][k] -= G[j][k] * mul) %= MOD;
            }
        }
        // DEBUG_MAT(G);
    }

    rep(i, n)(ans *= G[i][i]) %= MOD;
    return (ans + MOD) % MOD;
}

signed main() {
    cin >> n >> m;
    int cntm = 0;
    rep(i, m) {
        int u, v;
        cin >> u >> v;
        if (u == v) continue;
        u--;
        v--;
        G[u].push_back(v);
        G[v].push_back(u);
        cntm++;
    }
    m = cntm;

    set_fact(10000);

    // ren_num[mask] : mask に含まれる頂点だけを考えたグラフの全域木の個数
    // https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence#cite_note-4 より計算
    vl ren_num(1 << n);
    vl in_enum(1 << n);
    rep(i, 1 << n) {
        if (i == 0) continue;
        UnionFind uf(n);
        Zaatsu za;
        int cnt = 0;
        // num: 次数
        vi num(n);
        vector<pii> uv;
        rep(u, n) {
            if (!contain(i, u)) continue;
            top[i] = u;
            cnt++;
            za.add(u);
            for (int v : G[u]) {
                if (!contain(i, v)) continue;
                uf.unite(u, v);
                if (u < v) {
                    uv.emplace_back(u, v);
                }
                num[u]++;
            }
        }
        za.build();

        in_enum[i] = uv.size();

        if (uf.g != (n - cnt) + 1) {
            continue;
        }
        if (cnt == 1) {
            ren_num[i] = 1;
            continue;
        }

        static vll G3;
        G3.resize(cnt);
        rep(i, cnt) {
            G3[i].resize(cnt);
            fill(all(G3[i]), 0);
        }

        for (auto [u, v] : uv) {
            int uu = za[u], vv = za[v];
            G3[uu][vv] = G3[vv][uu] = -1;
            G3[uu][uu]++;
            G3[vv][vv]++;
        }

        G3.pop_back();
        rep(i, G3.size()) G3[i].pop_back();

        ren_num[i] = det(G3);

        // ren_num[i] = fact[cnt - 2];
        // for (int d : num) {
        //     if (d == 0) continue;
        //     (ren_num[i] *= rfact[d - 1]) %= MOD;
        // }
    }

    vl power2(10000);
    power2[0] = 1;
    rep(i, 9000) {
        power2[i + 1] = power2[i] * 2 % MOD;
    }

    int zen = (1 << n) - 1;
    // e_num[mask]: 集合 a と集合 mask の間の辺の数
    // vi e_num(1 << n, -1);
    // vi filled;
    ll ans = 0;
    rep(i, 1 << n) {
        int a = zen ^ i;
        if (a == 0) continue;

        // filled.push_back(0);
        // e_num[0] = 0;
        vi js;
        int j = i;
        do {
            js.push_back(j);
            j = (j - 1) & i;
        } while (j != i);

        // ここの reverse は特に意味ない
        reverse(all(js));

        for (int j : js) {
            int b = i ^ j;
            int c = j;

            // ここまでで a, b, c の三つに分けた
            if (b == 0 || c == 0) continue;

            // a, b, c それぞれの内部でのへんの貼り方は ren_num[a]
            ll add = ren_num[a] * ren_num[b] % MOD * ren_num[c] % MOD;

            // a-b と a-c の間にある辺の数を計算
            int ab = in_enum[a | b] - in_enum[a] - in_enum[b];
            int ac = in_enum[a | c] - in_enum[a] - in_enum[c];
            if (ab == 0 || ac == 0) continue;

            // b-c に辺が貼れないため、a-b と a-c にそれぞれ一つ以上の辺が必要
            add = add * (power2[ab] - 1) % MOD * (power2[ac] - 1) % MOD;
            (ans += add) %= MOD;
        }

        // for (int u : filled) {
        //     e_num[u] = -1;
        // }
        // filled.clear();
    }
    cout << (ans % MOD + MOD) % MOD << endl;
}
accuracy: 1
time_ms: 1520
memory_kb: 18588
final_rank: 14

code_snippet: #include<bits/stdc++.h>
#define overload4(_1, _2, _3, _4, name, ...) name
#define rep1(i, n) for (ll i = 0; i < ll(n); ++i)
#define rep2(i, s, n) for (ll i = ll(s); i < ll(n); ++i)
#define rep3(i, s, n, d) for(ll i = ll(s); i < ll(n); i+=d)
#define rep(...) overload4(__VA_ARGS__,rep3,rep2,rep1)(__VA_ARGS__)
#define rrep1(i, n) for (ll i = ll(n)-1; i >= 0; i--)
#define rrep2(i, n, t) for (ll i = ll(n)-1; i >= (ll)t; i--)
#define rrep3(i, n, t, d) for (ll i = ll(n)-1; i >= (ll)t; i-=d)
#define rrep(...) overload4(__VA_ARGS__,rrep3,rrep2,rrep1)(__VA_ARGS__)
#define all(a) a.begin(),a.end()
#define rall(a) a.rbegin(),a.rend()
#define SUM(a) accumulate(all(a),0LL)
#define MIN(a) *min_element(all(a))
#define MAX(a) *max_element(all(a))
#define SZ(a) int(a.size())
#define popcount(x) __builtin_popcountll(x)
#define pf push_front
#define pb push_back
#define ef emplace_front
#define eb emplace_back
#define ppf pop_front
#define ppb pop_back
#ifdef __LOCAL
#define debug(...) { cout << #__VA_ARGS__; cout << ": "; print(__VA_ARGS__); cout << flush; }
#else
#define debug(...) void(0);
#endif
#define INT(...) int __VA_ARGS__;scan(__VA_ARGS__)
#define LL(...) ll __VA_ARGS__;scan(__VA_ARGS__)
#define STR(...) string __VA_ARGS__;scan(__VA_ARGS__)
#define CHR(...) char __VA_ARGS__;scan(__VA_ARGS__)
#define DBL(...) double __VA_ARGS__;scan(__VA_ARGS__)
#define LD(...) ld __VA_ARGS__;scan(__VA_ARGS__)
using namespace std;
using ll = long long;
using ld = long double;
using P = pair<int, int>;
using LP = pair<ll, ll>;
using vi = vector<int>;
using vvi = vector<vi>;
using vvvi = vector<vvi>;
using vl = vector<ll>;
using vvl = vector<vl>;
using vvvl = vector<vvl>;
using vd = vector<double>;
using vvd = vector<vd>;
using vs = vector<string>;
using vc = vector<char>;
using vvc = vector<vc>;
using vb = vector<bool>;
using vvb = vector<vb>;
using vp = vector<P>;
using vvp = vector<vp>;

template<class S, class T>
istream &operator>>(istream &is, pair<S, T> &p) { return is >> p.first >> p.second; }

template<class S, class T>
ostream &operator<<(ostream &os, const pair<S, T> &p) { return os << '{' << p.first << ", " << p.second << '}'; }

template<class S, class T, class U>
istream &operator>>(istream &is, tuple<S, T, U> &t) { return is >> get<0>(t) >> get<1>(t) >> get<2>(t); }

template<class S, class T, class U>
ostream &operator<<(ostream &os, const tuple<S, T, U> &t) {
    return os << '{' << get<0>(t) << ", " << get<1>(t) << ", " << get<2>(t) << '}';
}

template<class T>
istream &operator>>(istream &is, vector<T> &v) {
    for (T &t : v) { is >> t; }
    return is;
}

template<class T>
ostream &operator<<(ostream &os, const vector<T> &v) {
    os << '[';
    rep(i, v.size()) os << v[i] << (i == int(v.size() - 1) ? "" : ", ");
    return os << ']';
}

template<class T>
ostream &operator<<(ostream &os, const set<T> &st) {
    os << '{';
    auto it = st.begin();
    while (it != st.end()) {
        os << (it == st.begin() ? "" : ", ") << *it;
        it++;
    }
    return os << '}';
}

template<class T>
void vecout(const vector<T> &v, char div = '\n') {
    rep(i, v.size()) cout << v[i] << (i == int(v.size() - 1) ? '\n' : div);
}

template<class T>
bool chmin(T &a, T b) {
    if (a > b) {
        a = b;
        return true;
    }
    return false;
}

template<class T>
bool chmax(T &a, T b) {
    if (a < b) {
        a = b;
        return true;
    }
    return false;
}

void scan() {}

template<class Head, class... Tail>
void scan(Head &head, Tail &... tail) {
    cin >> head;
    scan(tail...);
}

template<class T>
void print(const T &t) { cout << t << '\n'; }

template<class Head, class... Tail>
void print(const Head &head, const Tail &... tail) {
    cout << head << ' ';
    print(tail...);
}

template<class... T>
void fin(const T &... a) {
    print(a...);
    exit(0);
}

struct Init_io {
    Init_io() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        cout.tie(nullptr);
        cout << boolalpha << fixed << setprecision(15);
        cerr << boolalpha << fixed << setprecision(15);
    }
} init_io;

const string yes[] = {"no", "yes"};
const string Yes[] = {"No", "Yes"};
const string YES[] = {"NO", "YES"};
const int inf = 1001001001;
const ll linf = 1001001001001001001;

void rearrange(const vi &ord) {}

template<class T, class... Tail>
void rearrange(const vi &ord, vector<T> &head, Tail &...tail) {
    assert(ord.size() == head.size());
    vector<T> ori = head;
    rep(i, ord.size()) head[i] = ori[ord[i]];
    rearrange(ord, tail...);
}

template<class T, class... Tail>
void sort_by(vector<T> &head, Tail &... tail) {
    vi ord(head.size());
    iota(all(ord), 0);
    sort(all(ord), [&](int i, int j) { return head[i] < head[j]; });
    rearrange(ord, head, tail...);
}

template<class T, class S>
vector<T> cumsum(const vector<S> &v, bool shift_one = true) {
    int n = v.size();
    vector<T> res;
    if (shift_one) {
        res.resize(n + 1);
        rep(i, n) res[i + 1] = res[i] + v[i];
    } else {
        res.resize(n);
        if (n) {
            res[0] = v[0];
            rep(i, 1, n) res[i] = res[i - 1] + v[i];
        }
    }
    return res;
}

vvi graph(int n, int m, bool directed = false, int origin = 1) {
    vvi G(n);
    rep(_, m) {
        INT(u, v);
        u -= origin, v -= origin;
        G[u].pb(v);
        if (!directed) G[v].pb(u);
    }
    return G;
}

template<class T>
vector<vector<pair<int, T>>> weighted_graph(int n, int m, bool directed = false, int origin = 1) {
    vector<vector<pair<int, T>>> G(n);
    rep(_, m) {
        int u, v;
        T w;
        scan(u, v, w);
        u -= origin, v -= origin;
        G[u].eb(v, w);
        if (!directed) G[v].eb(u, w);
    }
    return G;
}

class unionfind {
    int n;
    vector<int> par, rank;
public:
    unionfind(int n) : n(n), par(n, -1), rank(n, 0) {}
    
    int root(int x) {
        if (par[x] < 0) return x;
        else return par[x] = root(par[x]);
    }
    
    bool is_root(int x) { return root(x) == x; }
    
    bool same(int x, int y) { return root(x) == root(y); };
    
    bool merge(int x, int y) {
        x = root(x);
        y = root(y);
        if (x == y) return false;
        if (rank[x] < rank[y]) swap(x, y);
        if (rank[x] == rank[y]) rank[x]++;
        par[x] += par[y];
        par[y] = x;
        return true;
    }
    
    int size(int x) { return -par[root(x)]; };
    
    vi roots() {
        vi res;
        rep(i, n) if (root(i) == i) res.pb(i);
        return res;
    }
    
    bool connected() {
        return roots().size() == 1;
    }
};

template<int mod>
class modint {
    ll x;
public:
    constexpr modint(ll x = 0) : x((x % mod + mod) % mod) {}
    
    static constexpr int get_mod() { return mod; }
    
    constexpr int val() const { return x; }
    
    constexpr modint operator-() const { return modint(-x); }
    
    constexpr modint &operator+=(const modint &a) {
        if ((x += a.val()) >= mod) x -= mod;
        return *this;
    }
    
    constexpr modint &operator++() { return *this += 1; }
    
    constexpr modint &operator-=(const modint &a) {
        if ((x += mod - a.val()) >= mod) x -= mod;
        return *this;
    }
    
    constexpr modint &operator--() { return *this -= 1; }
    
    constexpr modint &operator*=(const modint &a) {
        (x *= a.val()) %= mod;
        return *this;
    }
    
    constexpr modint operator+(const modint &a) const {
        modint res(*this);
        return res += a;
    }
    
    constexpr modint operator-(const modint &a) const {
        modint res(*this);
        return res -= a;
    }
    
    constexpr modint operator*(const modint &a) const {
        modint res(*this);
        return res *= a;
    }
    
    constexpr modint pow(ll t) const {
        modint res = 1, a(*this);
        while (t > 0) {
            if (t & 1) res *= a;
            t >>= 1;
            a *= a;
        }
        return res;
    }
    
    template<int m>
    friend istream &operator>>(istream &, modint<m> &);
    
    // for prime mod
    constexpr modint inv() const { return pow(mod - 2); }
    
    constexpr modint &operator/=(const modint &a) { return *this *= a.inv(); }
    
    constexpr modint operator/(const modint &a) const {
        modint res(*this);
        return res /= a;
    }
};

using modint998244353 = modint<998244353>;
using modint1000000007 = modint<1000000007>;

template<int mod>
istream &operator>>(istream &is, modint<mod> &a) { return is >> a.x; }

template<int mod>
constexpr ostream &operator<<(ostream &os, const modint<mod> &a) { return os << a.val(); }

template<int mod>
constexpr bool operator==(const modint<mod> &a, const modint<mod> &b) { return a.val() == b.val(); }

template<int mod>
constexpr bool operator!=(const modint<mod> &a, const modint<mod> &b) { return a.val() != b.val(); }

template<int mod>
constexpr modint<mod> &operator++(modint<mod> &a) { return a += 1; }

template<int mod>
constexpr modint<mod> &operator--(modint<mod> &a) { return a -= 1; }

using mint = modint998244353;

using vm = vector<mint>;
using vvm = vector<vm>;

template<typename T>
class matrix : public vector<vector<T>> {
public:
    using vector<vector<T>>::vector;
    
    constexpr int get_h() const { return this->size(); }
    
    constexpr int get_w() const { return (get_h() ? (*this)[0].size() : 0); }
    
    constexpr matrix &operator+=(const matrix &a) {
        rep(i, get_h()) rep(j, get_w()) (*this)[i][j] += a[i][j];
        return *this;
    }
    
    constexpr matrix &operator*=(const int &k) {
        rep(i, get_h()) rep(j, get_w()) (*this)[i][j] *= k;
        return *this;
    }
    
    constexpr matrix &operator-=(const matrix &a) {
        *this += a * (-1);
        return *this;
    }
    
    constexpr matrix operator+(const matrix &a) const { return res(*this) += a; }
    
    constexpr matrix operator*(const int &k) const { return res(*this) *= k; }
    
    constexpr matrix operator-(const matrix &a) const { return res(*this) -= a; }
    
    constexpr matrix operator*(const matrix &a) const {
        int h = get_h(), w = get_w(), ah = a.get_h(), aw = a.get_w();
        assert(w == ah);
        matrix res(h, vector<T>(aw));
        rep(i, h) rep(j, w) rep(k, aw) res[i][k] += (*this)[i][j] * a[j][k];
        return res;
    }
    
    constexpr matrix &operator*=(const matrix &a) { return *this = *this * a; }
    
    constexpr matrix pow(ll t) const {
        int h = get_h(), w = get_w();
        assert(h == w);
        matrix res(h, vector<T>(w)), a(*this);
        rep(i, get_h()) res[i][i] = 1;
        while (t > 0) {
            if (t & 1) res *= a;
            t >>= 1;
            a *= a;
        }
        return res;
    }
};

using mat = matrix<mint>;

const double eps = 1e-9;

template<class T>
T determinant(matrix<T> a) {
    assert(a.get_h() == a.get_w());
    int n = a.get_h();
    T res = 1;
    rep(col, n) {
        int pivot = -1;
        rep(row, col, n) {
            if (a[row][col] != T(0)) pivot = row;
        }
        if (pivot == -1) return 0;
        if (pivot != col) {
            res *= -1;
            swap(a[pivot], a[col]);
        }
        res *= a[col][col];
        rep(row, n) {
            if (row == col) continue;
            if (a[row][col] == T(0)) continue;
            T fac = a[row][col] / a[col][col];
            rep(col2, n) a[row][col2] -= a[col][col2] * fac;
        }
    }
    return res;
}

template<>
double determinant(matrix<double> a) {
    assert(a.get_h() == a.get_w());
    int n = a.get_h();
    double res = 1;
    rep(col, n) {
        int pivot = -1;
        double mx = eps;
        rep(row, col, n) {
            if (abs(a[row][col]) > mx) {
                mx = abs(a[row][col]);
                pivot = row;
            }
        }
        if (pivot == -1) return 0;
        if (pivot != col) {
            res *= -1;
            swap(a[pivot], a[col]);
        }
        res *= a[col][col];
        rep(row, n) {
            if (row == col) continue;
            if (abs(a[row][col]) <= eps) continue;
            double fac = a[row][col] / a[col][col];
            rep(col2, n) a[row][col2] -= a[col][col2] * fac;
        }
    }
    return res;
}

int main() {
    INT(n, m);
    vvi G = graph(n, m);
    vm cnt(1 << n);
    rep(bit, 1, 1 << n) {
        mat mt(n, vm(n));
        int pos;
        rep(i, n) {
            if (~bit >> i & 1) continue;
            pos = i;
            for (int j : G[i]) {
                if (~bit >> j & 1) continue;
                mt[i][i] += 1;
                mt[i][j] -= 1;
            }
        }
        rep(i, n) {
            if (bit >> i & 1) continue;
            mt[i][i] += 1;
            mt[pos][pos] += 1;
            mt[i][pos] -= 1;
            mt[pos][i] -= 1;
        }
        rep(i, n) mt[i].ppb();
        mt.ppb();
        cnt[bit] = determinant(mt);
    }
    debug(cnt);
    mint ans = 0;
    vm pw(4000);
    rep(i, 4000) pw[i] = mint(2).pow(i);
    rep(d, 1, 1 << n) {
        debug(d);
        vi eg(n);
        rep(i, n) {
            if (~d >> i & 1) continue;
            for (int j : G[i]) {
                if (d >> j & 1) continue;
                eg[j]++;
            }
        }
        vi ls;
        rep(i, n) if (~d >> i & 1) ls.pb(i);
        if (SZ(ls) < 2) continue;
        int sz = SZ(ls);
        int pre = 0;
        int be = 0, ce = SUM(eg);
        int bb = 0, cb = (1 << n) - 1 - d;
        rep(b, 1, 1 << sz) {
            int now = b ^ (b >> 1);
            int dis = pre ^ now;
            debug(now, dis);
            int pos = __builtin_ctz(dis);
            if (now >> pos & 1) {
                be += eg[ls[pos]];
                ce -= eg[ls[pos]];
                bb += 1 << ls[pos];
                cb -= 1 << ls[pos];
            } else {
                be -= eg[ls[pos]];
                ce += eg[ls[pos]];
                bb -= 1 << ls[pos];
                cb += 1 << ls[pos];
            }
            pre = now;
            ans += cnt[d] * cnt[bb] * cnt[cb] * (pw[be] - 1) * (pw[ce] - 1);
        }
    }
    fin(ans);
}
accuracy: 1
time_ms: 2600
memory_kb: 4520
final_rank: 16

code_snippet: #include <stdio.h>
#include <bits/stdc++.h>

#include <cassert>
#include <numeric>
#include <type_traits>

#ifdef _MSC_VER
#include <intrin.h>
#endif


#include <utility>

#ifdef _MSC_VER
#include <intrin.h>
#endif

namespace atcoder {

namespace internal {

constexpr long long safe_mod(long long x, long long m) {
    x %= m;
    if (x < 0) x += m;
    return x;
}

struct barrett {
    unsigned int _m;
    unsigned long long im;

    explicit barrett(unsigned int m) : _m(m), im((unsigned long long)(-1) / m + 1) {}

    unsigned int umod() const { return _m; }

    unsigned int mul(unsigned int a, unsigned int b) const {

        unsigned long long z = a;
        z *= b;
#ifdef _MSC_VER
        unsigned long long x;
        _umul128(z, im, &x);
#else
        unsigned long long x =
            (unsigned long long)(((unsigned __int128)(z)*im) >> 64);
#endif
        unsigned int v = (unsigned int)(z - x * _m);
        if (_m <= v) v += _m;
        return v;
    }
};

constexpr long long pow_mod_constexpr(long long x, long long n, int m) {
    if (m == 1) return 0;
    unsigned int _m = (unsigned int)(m);
    unsigned long long r = 1;
    unsigned long long y = safe_mod(x, m);
    while (n) {
        if (n & 1) r = (r * y) % _m;
        y = (y * y) % _m;
        n >>= 1;
    }
    return r;
}

constexpr bool is_prime_constexpr(int n) {
    if (n <= 1) return false;
    if (n == 2 || n == 7 || n == 61) return true;
    if (n % 2 == 0) return false;
    long long d = n - 1;
    while (d % 2 == 0) d /= 2;
    constexpr long long bases[3] = {2, 7, 61};
    for (long long a : bases) {
        long long t = d;
        long long y = pow_mod_constexpr(a, t, n);
        while (t != n - 1 && y != 1 && y != n - 1) {
            y = y * y % n;
            t <<= 1;
        }
        if (y != n - 1 && t % 2 == 0) {
            return false;
        }
    }
    return true;
}
template <int n> constexpr bool is_prime = is_prime_constexpr(n);

constexpr std::pair<long long, long long> inv_gcd(long long a, long long b) {
    a = safe_mod(a, b);
    if (a == 0) return {b, 0};

    long long s = b, t = a;
    long long m0 = 0, m1 = 1;

    while (t) {
        long long u = s / t;
        s -= t * u;
        m0 -= m1 * u;  // |m1 * u| <= |m1| * s <= b


        auto tmp = s;
        s = t;
        t = tmp;
        tmp = m0;
        m0 = m1;
        m1 = tmp;
    }
    if (m0 < 0) m0 += b / s;
    return {s, m0};
}

constexpr int primitive_root_constexpr(int m) {
    if (m == 2) return 1;
    if (m == 167772161) return 3;
    if (m == 469762049) return 3;
    if (m == 754974721) return 11;
    if (m == 998244353) return 3;
    int divs[20] = {};
    divs[0] = 2;
    int cnt = 1;
    int x = (m - 1) / 2;
    while (x % 2 == 0) x /= 2;
    for (int i = 3; (long long)(i)*i <= x; i += 2) {
        if (x % i == 0) {
            divs[cnt++] = i;
            while (x % i == 0) {
                x /= i;
            }
        }
    }
    if (x > 1) {
        divs[cnt++] = x;
    }
    for (int g = 2;; g++) {
        bool ok = true;
        for (int i = 0; i < cnt; i++) {
            if (pow_mod_constexpr(g, (m - 1) / divs[i], m) == 1) {
                ok = false;
                break;
            }
        }
        if (ok) return g;
    }
}
template <int m> constexpr int primitive_root = primitive_root_constexpr(m);

unsigned long long floor_sum_unsigned(unsigned long long n,
                                      unsigned long long m,
                                      unsigned long long a,
                                      unsigned long long b) {
    unsigned long long ans = 0;
    while (true) {
        if (a >= m) {
            ans += n * (n - 1) / 2 * (a / m);
            a %= m;
        }
        if (b >= m) {
            ans += n * (b / m);
            b %= m;
        }

        unsigned long long y_max = a * n + b;
        if (y_max < m) break;
        n = (unsigned long long)(y_max / m);
        b = (unsigned long long)(y_max % m);
        std::swap(m, a);
    }
    return ans;
}

}  // namespace internal

}  // namespace atcoder


#include <cassert>
#include <numeric>
#include <type_traits>

namespace atcoder {

namespace internal {

#ifndef _MSC_VER
template <class T>
using is_signed_int128 =
    typename std::conditional<std::is_same<T, __int128_t>::value ||
                                  std::is_same<T, __int128>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using is_unsigned_int128 =
    typename std::conditional<std::is_same<T, __uint128_t>::value ||
                                  std::is_same<T, unsigned __int128>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using make_unsigned_int128 =
    typename std::conditional<std::is_same<T, __int128_t>::value,
                              __uint128_t,
                              unsigned __int128>;

template <class T>
using is_integral = typename std::conditional<std::is_integral<T>::value ||
                                                  is_signed_int128<T>::value ||
                                                  is_unsigned_int128<T>::value,
                                              std::true_type,
                                              std::false_type>::type;

template <class T>
using is_signed_int = typename std::conditional<(is_integral<T>::value &&
                                                 std::is_signed<T>::value) ||
                                                    is_signed_int128<T>::value,
                                                std::true_type,
                                                std::false_type>::type;

template <class T>
using is_unsigned_int =
    typename std::conditional<(is_integral<T>::value &&
                               std::is_unsigned<T>::value) ||
                                  is_unsigned_int128<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using to_unsigned = typename std::conditional<
    is_signed_int128<T>::value,
    make_unsigned_int128<T>,
    typename std::conditional<std::is_signed<T>::value,
                              std::make_unsigned<T>,
                              std::common_type<T>>::type>::type;

#else

template <class T> using is_integral = typename std::is_integral<T>;

template <class T>
using is_signed_int =
    typename std::conditional<is_integral<T>::value && std::is_signed<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using is_unsigned_int =
    typename std::conditional<is_integral<T>::value &&
                                  std::is_unsigned<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using to_unsigned = typename std::conditional<is_signed_int<T>::value,
                                              std::make_unsigned<T>,
                                              std::common_type<T>>::type;

#endif

template <class T>
using is_signed_int_t = std::enable_if_t<is_signed_int<T>::value>;

template <class T>
using is_unsigned_int_t = std::enable_if_t<is_unsigned_int<T>::value>;

template <class T> using to_unsigned_t = typename to_unsigned<T>::type;

}  // namespace internal

}  // namespace atcoder


namespace atcoder {

namespace internal {

struct modint_base {};
struct static_modint_base : modint_base {};

template <class T> using is_modint = std::is_base_of<modint_base, T>;
template <class T> using is_modint_t = std::enable_if_t<is_modint<T>::value>;

}  // namespace internal

template <int m, std::enable_if_t<(1 <= m)>* = nullptr>
struct static_modint : internal::static_modint_base {
    using mint = static_modint;

  public:
    static constexpr int mod() { return m; }
    static mint raw(int v) {
        mint x;
        x._v = v;
        return x;
    }

    static_modint() : _v(0) {}
    template <class T, internal::is_signed_int_t<T>* = nullptr>
    static_modint(T v) {
        long long x = (long long)(v % (long long)(umod()));
        if (x < 0) x += umod();
        _v = (unsigned int)(x);
    }
    template <class T, internal::is_unsigned_int_t<T>* = nullptr>
    static_modint(T v) {
        _v = (unsigned int)(v % umod());
    }

    unsigned int val() const { return _v; }

    mint& operator++() {
        _v++;
        if (_v == umod()) _v = 0;
        return *this;
    }
    mint& operator--() {
        if (_v == 0) _v = umod();
        _v--;
        return *this;
    }
    mint operator++(int) {
        mint result = *this;
        ++*this;
        return result;
    }
    mint operator--(int) {
        mint result = *this;
        --*this;
        return result;
    }

    mint& operator+=(const mint& rhs) {
        _v += rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator-=(const mint& rhs) {
        _v -= rhs._v;
        if (_v >= umod()) _v += umod();
        return *this;
    }
    mint& operator*=(const mint& rhs) {
        unsigned long long z = _v;
        z *= rhs._v;
        _v = (unsigned int)(z % umod());
        return *this;
    }
    mint& operator/=(const mint& rhs) { return *this = *this * rhs.inv(); }

    mint operator+() const { return *this; }
    mint operator-() const { return mint() - *this; }

    mint pow(long long n) const {
        assert(0 <= n);
        mint x = *this, r = 1;
        while (n) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        return r;
    }
    mint inv() const {
        if (prime) {
            assert(_v);
            return pow(umod() - 2);
        } else {
            auto eg = internal::inv_gcd(_v, m);
            assert(eg.first == 1);
            return eg.second;
        }
    }

    friend mint operator+(const mint& lhs, const mint& rhs) {
        return mint(lhs) += rhs;
    }
    friend mint operator-(const mint& lhs, const mint& rhs) {
        return mint(lhs) -= rhs;
    }
    friend mint operator*(const mint& lhs, const mint& rhs) {
        return mint(lhs) *= rhs;
    }
    friend mint operator/(const mint& lhs, const mint& rhs) {
        return mint(lhs) /= rhs;
    }
    friend bool operator==(const mint& lhs, const mint& rhs) {
        return lhs._v == rhs._v;
    }
    friend bool operator!=(const mint& lhs, const mint& rhs) {
        return lhs._v != rhs._v;
    }

  private:
    unsigned int _v;
    static constexpr unsigned int umod() { return m; }
    static constexpr bool prime = internal::is_prime<m>;
};

template <int id> struct dynamic_modint : internal::modint_base {
    using mint = dynamic_modint;

  public:
    static int mod() { return (int)(bt.umod()); }
    static void set_mod(int m) {
        assert(1 <= m);
        bt = internal::barrett(m);
    }
    static mint raw(int v) {
        mint x;
        x._v = v;
        return x;
    }

    dynamic_modint() : _v(0) {}
    template <class T, internal::is_signed_int_t<T>* = nullptr>
    dynamic_modint(T v) {
        long long x = (long long)(v % (long long)(mod()));
        if (x < 0) x += mod();
        _v = (unsigned int)(x);
    }
    template <class T, internal::is_unsigned_int_t<T>* = nullptr>
    dynamic_modint(T v) {
        _v = (unsigned int)(v % mod());
    }

    unsigned int val() const { return _v; }

    mint& operator++() {
        _v++;
        if (_v == umod()) _v = 0;
        return *this;
    }
    mint& operator--() {
        if (_v == 0) _v = umod();
        _v--;
        return *this;
    }
    mint operator++(int) {
        mint result = *this;
        ++*this;
        return result;
    }
    mint operator--(int) {
        mint result = *this;
        --*this;
        return result;
    }

    mint& operator+=(const mint& rhs) {
        _v += rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator-=(const mint& rhs) {
        _v += mod() - rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator*=(const mint& rhs) {
        _v = bt.mul(_v, rhs._v);
        return *this;
    }
    mint& operator/=(const mint& rhs) { return *this = *this * rhs.inv(); }

    mint operator+() const { return *this; }
    mint operator-() const { return mint() - *this; }

    mint pow(long long n) const {
        assert(0 <= n);
        mint x = *this, r = 1;
        while (n) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        return r;
    }
    mint inv() const {
        auto eg = internal::inv_gcd(_v, mod());
        assert(eg.first == 1);
        return eg.second;
    }

    friend mint operator+(const mint& lhs, const mint& rhs) {
        return mint(lhs) += rhs;
    }
    friend mint operator-(const mint& lhs, const mint& rhs) {
        return mint(lhs) -= rhs;
    }
    friend mint operator*(const mint& lhs, const mint& rhs) {
        return mint(lhs) *= rhs;
    }
    friend mint operator/(const mint& lhs, const mint& rhs) {
        return mint(lhs) /= rhs;
    }
    friend bool operator==(const mint& lhs, const mint& rhs) {
        return lhs._v == rhs._v;
    }
    friend bool operator!=(const mint& lhs, const mint& rhs) {
        return lhs._v != rhs._v;
    }

  private:
    unsigned int _v;
    static internal::barrett bt;
    static unsigned int umod() { return bt.umod(); }
};
template <int id> internal::barrett dynamic_modint<id>::bt(998244353);

using modint998244353 = static_modint<998244353>;
using modint1000000007 = static_modint<1000000007>;
using modint = dynamic_modint<-1>;

namespace internal {

template <class T>
using is_static_modint = std::is_base_of<internal::static_modint_base, T>;

template <class T>
using is_static_modint_t = std::enable_if_t<is_static_modint<T>::value>;

template <class> struct is_dynamic_modint : public std::false_type {};
template <int id>
struct is_dynamic_modint<dynamic_modint<id>> : public std::true_type {};

template <class T>
using is_dynamic_modint_t = std::enable_if_t<is_dynamic_modint<T>::value>;

}  // namespace internal

}  // namespace atcoder


#include <algorithm>
#include <cassert>
#include <vector>

namespace atcoder {

struct dsu {
  public:
    dsu() : _n(0) {}
    explicit dsu(int n) : _n(n), parent_or_size(n, -1) {}

    int merge(int a, int b) {
        assert(0 <= a && a < _n);
        assert(0 <= b && b < _n);
        int x = leader(a), y = leader(b);
        if (x == y) return x;
        if (-parent_or_size[x] < -parent_or_size[y]) std::swap(x, y);
        parent_or_size[x] += parent_or_size[y];
        parent_or_size[y] = x;
        return x;
    }

    bool same(int a, int b) {
        assert(0 <= a && a < _n);
        assert(0 <= b && b < _n);
        return leader(a) == leader(b);
    }

    int leader(int a) {
        assert(0 <= a && a < _n);
        if (parent_or_size[a] < 0) return a;
        return parent_or_size[a] = leader(parent_or_size[a]);
    }

    int size(int a) {
        assert(0 <= a && a < _n);
        return -parent_or_size[leader(a)];
    }

    std::vector<std::vector<int>> groups() {
        std::vector<int> leader_buf(_n), group_size(_n);
        for (int i = 0; i < _n; i++) {
            leader_buf[i] = leader(i);
            group_size[leader_buf[i]]++;
        }
        std::vector<std::vector<int>> result(_n);
        for (int i = 0; i < _n; i++) {
            result[i].reserve(group_size[i]);
        }
        for (int i = 0; i < _n; i++) {
            result[leader_buf[i]].push_back(i);
        }
        result.erase(
            std::remove_if(result.begin(), result.end(),
                           [&](const std::vector<int>& v) { return v.empty(); }),
            result.end());
        return result;
    }

  private:
    int _n;
    std::vector<int> parent_or_size;
};

}  // namespace atcoder

using namespace atcoder;
using mint = modint998244353;
using namespace std;
#define rep(i,n) for (int i = 0; i < (n); ++i)
#define Inf 1000000001


mint bai = 1;
void Gauss(vector<vector<mint>> &A){
	int last = 0;
	rep(i,A[0].size()){
		int ind = -1;
		for(int j=last;j<A.size();j++){
			if(A[j][i]!=0){
				ind = j;
				break;
			}
		}
		if(ind==-1)continue;
		swap(A[last],A[ind]);
		if(last!=ind)bai *= -1;
		
		mint x = A[last][i].inv();
		rep(j,A[last].size())A[last][j] *= x;
		bai *= x;
		rep(j,A.size()){
			if(j==last)continue;
			x = A[j][i];
			if(x==0)continue;
			rep(k,A[j].size()){
				A[j][k] -= A[last][k] * x;
			}
		}
		
		last++;
	}
}
int main(){
	
	int N,M;
	cin>>N>>M;
	
	vector e(N,vector<bool>(N,false));
	
	rep(i,M){
		int a,b;
		cin>>a>>b;
		if(a==b)continue;
		e[a-1][b-1] = true;
		
	}
	
	vector cnt(1<<N,0);
	
	rep(i,1<<N){
		rep(j,N){
			for(int k=j;k<N;k++){
				if((i>>j)&1){
					if((i>>k)&1){
						if(e[j][k]){
							cnt[i] ++;
						}
					}
				}
			}
		}
	}
	/*
	rep(i,1<<N){
		cout<<i<<';'<<cnt[i]<<endl;
	}
	*/
	vector<mint> dp(1<<N,0);
	
	rep(i,1<<N){
		if(i==0)continue;
		vector<int> x(N,-1);
		int c = 0;
		rep(j,N){
			if((i>>j)&1){
				x[j] = c;
				c++;
			}
		}
		bai = 1;
		vector<vector<mint>> temp(c,vector<mint>(c,0));
		dsu D(c);
		rep(j,N){
			rep(k,N){
				if(e[j][k]){
					int u = x[j],v = x[k];
					
					if(x[j]!=-1&&x[k]!=-1){
						temp[u][v]--;
						temp[v][u]--;
						temp[u][u]++;
						temp[v][v]++;
						D.merge(u,v);
					}
				}
			}
		}
		if(D.size(0)!=c)continue;
		temp.pop_back();
		rep(j,temp.size())temp[j].pop_back();
		Gauss(temp);
		
		
		
		dp[i] = 1;
		rep(j,c-1){
			dp[i] *= temp[j][j];
		}
		dp[i] /= bai;
		
		
	}
	vector<mint> pow2(100);
	rep(i,99){
		pow2[i] = mint(2).pow(i);
	}
	mint ans = 0;
	rep(i,1<<N){
		
		int S = i;
		S ^= (1<<N)-1;
		for(int T=S;T!=0;T = ((T-1)&S)){
			ans += dp[i] * dp[T] * dp[S^T] * (pow2[(cnt[i|T] - cnt[i] - cnt[T])]-1) * (pow2[(cnt[i|(S^T)] - cnt[i] - cnt[S^T])]-1);
		}
		//cout<<i<<','<<ans.val()<<endl;
	}
	
	cout<<ans.val()<<endl;
	
	return 0;
}
accuracy: 1
time_ms: 1050
memory_kb: 4132
final_rank: 2

code_snippet: #define LOCAL
#include <bits/stdc++.h>
using namespace std;
#pragma region Macros
typedef long long ll;
typedef __int128_t i128;
typedef unsigned int uint;
typedef unsigned long long ull;
#define ALL(x) (x).begin(), (x).end()

template <typename T> istream& operator>>(istream& is, vector<T>& v) {
    for (T& x : v) is >> x;
    return is;
}
template <typename T> ostream& operator<<(ostream& os, const vector<T>& v) {
    for (int i = 0; i < (int)v.size(); i++) {
        os << v[i] << (i + 1 == (int)v.size() ? "" : " ");
    }
    return os;
}
template <typename T, typename U> ostream& operator<<(ostream& os, const pair<T, U>& p) {
    os << '(' << p.first << ',' << p.second << ')';
    return os;
}
template <typename T, typename U> ostream& operator<<(ostream& os, const map<T, U>& m) {
    os << '{';
    for (auto itr = m.begin(); itr != m.end();) {
        os << '(' << itr->first << ',' << itr->second << ')';
        if (++itr != m.end()) os << ',';
    }
    os << '}';
    return os;
}
template <typename T, typename U> ostream& operator<<(ostream& os, const unordered_map<T, U>& m) {
    os << '{';
    for (auto itr = m.begin(); itr != m.end();) {
        os << '(' << itr->first << ',' << itr->second << ')';
        if (++itr != m.end()) os << ',';
    }
    os << '}';
    return os;
}
template <typename T> ostream& operator<<(ostream& os, const set<T>& s) {
    os << '{';
    for (auto itr = s.begin(); itr != s.end();) {
        os << *itr;
        if (++itr != s.end()) os << ',';
    }
    os << '}';
    return os;
}
template <typename T> ostream& operator<<(ostream& os, const multiset<T>& s) {
    os << '{';
    for (auto itr = s.begin(); itr != s.end();) {
        os << *itr;
        if (++itr != s.end()) os << ',';
    }
    os << '}';
    return os;
}
template <typename T> ostream& operator<<(ostream& os, const unordered_set<T>& s) {
    os << '{';
    for (auto itr = s.begin(); itr != s.end();) {
        os << *itr;
        if (++itr != s.end()) os << ',';
    }
    os << '}';
    return os;
}
template <typename T> ostream& operator<<(ostream& os, const deque<T>& v) {
    for (int i = 0; i < (int)v.size(); i++) {
        os << v[i] << (i + 1 == (int)v.size() ? "" : " ");
    }
    return os;
}

template <int i, typename T> void print_tuple(ostream&, const T&) {}
template <int i, typename T, typename H, class... Args> void print_tuple(ostream& os, const T& t) {
    if (i) os << ',';
    os << get<i>(t);
    print_tuple<i + 1, T, Args...>(os, t);
}
template <typename... Args> ostream& operator<<(ostream& os, const tuple<Args...>& t) {
    os << '{';
    print_tuple<0, tuple<Args...>, Args...>(os, t);
    return os << '}';
}

void debug_out() { cerr << '\n'; }
template <class Head, class... Tail> void debug_out(Head&& head, Tail&&... tail) {
    cerr << head;
    if (sizeof...(Tail) > 0) cerr << ", ";
    debug_out(move(tail)...);
}
#ifdef LOCAL
#define debug(...)                                                                   \
    cerr << " ";                                                                     \
    cerr << #__VA_ARGS__ << " :[" << __LINE__ << ":" << __FUNCTION__ << "]" << '\n'; \
    cerr << " ";                                                                     \
    debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

template <typename T> T gcd(T x, T y) { return y != 0 ? gcd(y, x % y) : x; }
template <typename T> T lcm(T x, T y) { return x / gcd(x, y) * y; }

int topbit(signed t) { return t == 0 ? -1 : 31 - __builtin_clz(t); }
int topbit(long long t) { return t == 0 ? -1 : 63 - __builtin_clzll(t); }
int botbit(signed a) { return a == 0 ? 32 : __builtin_ctz(a); }
int botbit(long long a) { return a == 0 ? 64 : __builtin_ctzll(a); }
int popcount(signed t) { return __builtin_popcount(t); }
int popcount(long long t) { return __builtin_popcountll(t); }
bool ispow2(int i) { return i && (i & -i) == i; }

template <class T> T ceil(T x, T y) {
    assert(y >= 1);
    return (x > 0 ? (x + y - 1) / y : x / y);
}
template <class T> T floor(T x, T y) {
    assert(y >= 1);
    return (x > 0 ? x / y : (x - y + 1) / y);
}

template <class T1, class T2> inline bool chmin(T1& a, T2 b) {
    if (a > b) {
        a = b;
        return true;
    }
    return false;
}
template <class T1, class T2> inline bool chmax(T1& a, T2 b) {
    if (a < b) {
        a = b;
        return true;
    }
    return false;
}
#pragma endregion

#include <cassert>
#include <cstdint>
#include <iostream>

template <uint64_t Modulus> class modint {
    using i64 = int64_t;
    using u32 = uint32_t;
    using u64 = uint64_t;

    static_assert(Modulus < static_cast<uint32_t>(1) << 31, "Modulus must be less than 2**31");
    static constexpr u32 mod = Modulus;
    u32 v;

public:
    constexpr modint(const i64 x = 0) noexcept : v(x < 0 ? mod - 1 - (-(x + 1) % mod) : x % mod) {}
    constexpr u32& value() noexcept { return v; }
    constexpr const u32& value() const noexcept { return v; }
    constexpr modint operator+(const modint& rhs) const noexcept { return modint(*this) += rhs; }
    constexpr modint operator-(const modint& rhs) const noexcept { return modint(*this) -= rhs; }
    constexpr modint operator*(const modint& rhs) const noexcept { return modint(*this) *= rhs; }
    constexpr modint operator/(const modint& rhs) const noexcept { return modint(*this) /= rhs; }
    constexpr modint& operator+=(const modint& rhs) noexcept {
        v += rhs.v;
        if (v >= mod) v -= mod;
        return *this;
    }
    constexpr modint& operator-=(const modint& rhs) noexcept {
        if (v < rhs.v) v += mod;
        v -= rhs.v;
        return *this;
    }
    constexpr modint& operator*=(const modint& rhs) noexcept {
        v = (u64)v * rhs.v % mod;
        return *this;
    }
    constexpr modint& operator/=(const modint& rhs) noexcept { return *this *= rhs.inv(); }
    constexpr modint pow(u64 exp) const noexcept {
        assert(0 <= exp);
        modint self(*this), res(1);
        while (exp > 0) {
            if (exp & 1) res *= self;
            self *= self;
            exp >>= 1;
        }
        return res;
    }
    constexpr modint inv() const noexcept {
        assert(*this != 0);
        return pow(mod - 2);
    }
    constexpr modint& operator++() noexcept {
        if (++v == mod) v = 0;
        return *this;
    }
    constexpr modint& operator--() noexcept {
        if (v == 0) v = mod;
        return --v, *this;
    }
    constexpr modint operator++(int) noexcept {
        modint t = *this;
        return ++*this, t;
    }
    constexpr modint operator--(int) noexcept {
        modint t = *this;
        return --*this, t;
    }
    constexpr modint operator-() const noexcept { return modint(mod - v); }
    template <class T> friend constexpr modint operator+(T x, modint y) noexcept { return modint(x) + y; }
    template <class T> friend constexpr modint operator-(T x, modint y) noexcept { return modint(x) - y; }
    template <class T> friend constexpr modint operator*(T x, modint y) noexcept { return modint(x) * y; }
    template <class T> friend constexpr modint operator/(T x, modint y) noexcept { return modint(x) / y; }
    constexpr bool operator==(const modint& rhs) const noexcept { return v == rhs.v; }
    constexpr bool operator!=(const modint& rhs) const noexcept { return v != rhs.v; }
    constexpr bool operator!() const noexcept { return !v; }
    friend std::istream& operator>>(std::istream& s, modint& rhs) noexcept {
        i64 v;
        rhs = modint{(s >> v, v)};
        return s;
    }
    friend std::ostream& operator<<(std::ostream& s, const modint& rhs) noexcept { return s << rhs.v; }
};

/**
 * @brief modint
 * @docs docs/modulo/modint.md
 */

/**
 * @brief Matrix
 * @docs docs/linearalgebra/Matrix.md
 */
template <class T> struct Matrix {
    vector<vector<T>> A;
    Matrix(size_t n, size_t m) : A(n, vector<T>(m, 0)) {}
    Matrix(size_t n) : A(n, vector<T>(n, 0)) {}
    size_t height() const { return A.size(); }
    size_t width() const { return A[0].size(); }
    inline const vector<T>& operator[](int i) const { return A[i]; }
    inline vector<T>& operator[](int i) { return A[i]; }
    static Matrix I(size_t n) {
        Matrix res(n);
        for (int i = 0; i < n; i++) res[i][i] = 1;
        return res;
    }
    Matrix operator+(const Matrix& B) const { return Matrix(*this) += B; }
    Matrix operator-(const Matrix& B) const { return Matrix(*this) -= B; }
    Matrix operator*(const Matrix& B) const { return Matrix(*this) *= B; }
    Matrix operator^(const long long k) const { return Matrix(*this) ^= k; }
    Matrix& operator+=(const Matrix& B) {
        size_t n = height(), m = width();
        assert(n == B.height() && m == B.width());
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                (*this)[i][j] += B[i][j];
            }
        }
        return *this;
    }
    Matrix& operator-=(const Matrix& B) {
        size_t n = height(), m = width();
        assert(n == B.height() && m == B.width());
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                (*this)[i][j] -= B[i][j];
            }
        }
        return *this;
    }
    Matrix& operator*=(const Matrix& B) {
        size_t n = height(), m = B.width(), l = width();
        assert(l == B.height());
        vector<vector<T>> C(n, vector<T>(m, 0));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                for (int k = 0; k < l; k++) {
                    C[i][j] += (*this)[i][k] * B[k][j];
                }
            }
        }
        A.swap(C);
        return *this;
    }
    Matrix& operator^=(long long k) {
        Matrix res = Matrix::I(height());
        while (k > 0) {
            if (k & 1) res *= *this;
            *this *= *this;
            k >>= 1LL;
        }
        A.swap(res.A);
        return *this;
    }
    T determinant() {
        Matrix B(*this);
        T res = 1;
        for (int i = 0; i < width(); i++) {
            int pivot = -1;
            for (int j = i; j < height(); j++) {
                if (B[j][i] != 0) {
                    pivot = j;
                }
            }
            if (pivot < 0) return 0;
            if (pivot != i) {
                res *= -1;
                swap(B[i], B[pivot]);
            }
            res *= B[i][i];
            T v = T(1) / B[i][i];
            for (int j = 0; j < width(); j++) B[i][j] *= v;
            for (int j = i + 1; j < height(); j++) {
                T w = B[j][i];
                for (int k = 0; k < width(); k++) {
                    B[j][k] -= B[i][k] * w;
                }
            }
        }
        return res;
    }
};

/**
 * @brief グレイコード
 * @docs docs/util/gray_code.md
 */
vector<int> gray_code(int n) {
    vector<int> res(1 << n);
    for (int i = 0; i < (int)res.size(); i++) res[i] = i ^ (i >> 1);
    return res;
}

const int INF = 1e9;
const long long IINF = 1e18;
const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const char dir[4] = {'D', 'R', 'U', 'L'};
// const long long MOD = 1000000007;
const long long MOD = 998244353;

using mint = modint<MOD>;

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    int N, M;
    cin >> N >> M;
    vector<vector<int>> G(N, vector<int>(N, 0));
    for (; M--;) {
        int a, b;
        cin >> a >> b;
        a--, b--;
        G[a][b] = G[b][a] = true;
    }

    auto calc1 = [&](int mask) -> mint {  // mask の最小全域木の個数
        if (mask == 0) return 0;
        if (popcount(mask) == 1) return 1;
        vector<int> vs;
        for (int i = 0; i < N; i++) {
            if (mask >> i & 1) {
                vs.emplace_back(i);
            }
        }
        int n = vs.size();
        Matrix<mint> M(n);
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (G[vs[i]][vs[j]]) {
                    M[i][i]++;
                    M[j][j]++;
                    M[i][j]--;
                    M[j][i]--;
                }
            }
        }
        M.A.pop_back();
        for (int i = 0; i < n - 1; i++) M.A[i].pop_back();
        return M.determinant();
    };
    vector<mint> tree(1 << N);
    for (int mask = 0; mask < (1 << N); mask++) tree[mask] = calc1(mask);

    vector<vector<int>> es(1 << N, vector<int>(N, 0));  // i と連結成分 mask を結ぶ辺の数
    for (int mask = 0; mask < (1 << N); mask++) {
        for (int i = 0; i < N; i++) {
            if (mask >> i & 1) continue;
            for (int j = 0; j < N; j++) {
                es[mask][i] += ((mask >> j & 1) && G[i][j]);
            }
        }
    }
    vector<vector<int>> gray(N);
    for (int i = 1; i < N; i++) gray[i] = gray_code(i);
    // for (int mask = 1; mask + 1 < (1 << N); mask++) {
    //     int rest = ((1 << N) - 1) ^ mask;
    //     vector<int> vs;
    //     for (int i = 0; i < N; i++) {
    //         if (rest >> i & 1) {
    //             vs.emplace_back(i);
    //         }
    //     }
    //     int flag = popcount(rest);
    //     int cur = 0, sum = 0;
    //     for (int i = 0; i + 1 < (1 << flag); i++) {
    //         int diff = gray[flag][i] ^ gray[flag][i + 1];
    //         int v = vs[topbit(diff)];
    //         if (gray[flag][i] < gray[flag][i + 1]) {
    //             cur ^= 1 << v;
    //             sum += es[mask][v];
    //         } else {
    //             cur ^= 1 << v;
    //             sum -= es[mask][v];
    //         }
    //         mp[mask][cur] = sum;
    //     }
    // }
    vector<mint> power(N * N + 1, 1);
    for (int i = 0; i < N * N; i++) power[i + 1] = power[i] * 2;
    mint ans = 0;
    vector<int> mp(1 << N);  // mask と sub を結ぶ辺の数

    for (int mask = 1; mask + 1 < (1 << N); mask++) {
        int rest = ((1 << N) - 1) ^ mask;
        vector<int> vs;
        for (int i = 0; i < N; i++) {
            if (rest >> i & 1) {
                vs.emplace_back(i);
            }
        }
        int flag = popcount(rest);
        int cur = 0, sum = 0;
        for (int i = 0; i + 1 < (1 << flag); i++) {
            int diff = gray[flag][i] ^ gray[flag][i + 1];
            int v = vs[topbit(diff)];
            if (gray[flag][i] < gray[flag][i + 1]) {
                cur ^= 1 << v;
                sum += es[mask][v];
            } else {
                cur ^= 1 << v;
                sum -= es[mask][v];
            }
            mp[cur] = sum;
        }

        for (int sub = rest; sub > 0; sub = (sub - 1) & rest) {
            if (sub == rest) continue;
            int D = mask, B = sub, C = rest ^ B;
            ans += tree[B] * tree[C] * tree[D] * (power[mp[B]] - 1) * (power[mp[C]] - 1);
        }
        mp.clear();
    }

    cout << ans << '\n';
    return 0;
}
accuracy: 1
time_ms: 1140
memory_kb: 18020
final_rank: 12

code_snippet: #pragma GCC optimize("O3")
#include<bits/stdc++.h>

#include <cassert>
#include <numeric>
#include <type_traits>

#ifdef _MSC_VER
#include <intrin.h>
#endif


#include <utility>

#ifdef _MSC_VER
#include <intrin.h>
#endif

namespace atcoder {

namespace internal {

constexpr long long safe_mod(long long x, long long m) {
    x %= m;
    if (x < 0) x += m;
    return x;
}

struct barrett {
    unsigned int _m;
    unsigned long long im;

    explicit barrett(unsigned int m) : _m(m), im((unsigned long long)(-1) / m + 1) {}

    unsigned int umod() const { return _m; }

    unsigned int mul(unsigned int a, unsigned int b) const {

        unsigned long long z = a;
        z *= b;
#ifdef _MSC_VER
        unsigned long long x;
        _umul128(z, im, &x);
#else
        unsigned long long x =
            (unsigned long long)(((unsigned __int128)(z)*im) >> 64);
#endif
        unsigned int v = (unsigned int)(z - x * _m);
        if (_m <= v) v += _m;
        return v;
    }
};

constexpr long long pow_mod_constexpr(long long x, long long n, int m) {
    if (m == 1) return 0;
    unsigned int _m = (unsigned int)(m);
    unsigned long long r = 1;
    unsigned long long y = safe_mod(x, m);
    while (n) {
        if (n & 1) r = (r * y) % _m;
        y = (y * y) % _m;
        n >>= 1;
    }
    return r;
}

constexpr bool is_prime_constexpr(int n) {
    if (n <= 1) return false;
    if (n == 2 || n == 7 || n == 61) return true;
    if (n % 2 == 0) return false;
    long long d = n - 1;
    while (d % 2 == 0) d /= 2;
    constexpr long long bases[3] = {2, 7, 61};
    for (long long a : bases) {
        long long t = d;
        long long y = pow_mod_constexpr(a, t, n);
        while (t != n - 1 && y != 1 && y != n - 1) {
            y = y * y % n;
            t <<= 1;
        }
        if (y != n - 1 && t % 2 == 0) {
            return false;
        }
    }
    return true;
}
template <int n> constexpr bool is_prime = is_prime_constexpr(n);

constexpr std::pair<long long, long long> inv_gcd(long long a, long long b) {
    a = safe_mod(a, b);
    if (a == 0) return {b, 0};

    long long s = b, t = a;
    long long m0 = 0, m1 = 1;

    while (t) {
        long long u = s / t;
        s -= t * u;
        m0 -= m1 * u;  // |m1 * u| <= |m1| * s <= b


        auto tmp = s;
        s = t;
        t = tmp;
        tmp = m0;
        m0 = m1;
        m1 = tmp;
    }
    if (m0 < 0) m0 += b / s;
    return {s, m0};
}

constexpr int primitive_root_constexpr(int m) {
    if (m == 2) return 1;
    if (m == 167772161) return 3;
    if (m == 469762049) return 3;
    if (m == 754974721) return 11;
    if (m == 998244353) return 3;
    int divs[20] = {};
    divs[0] = 2;
    int cnt = 1;
    int x = (m - 1) / 2;
    while (x % 2 == 0) x /= 2;
    for (int i = 3; (long long)(i)*i <= x; i += 2) {
        if (x % i == 0) {
            divs[cnt++] = i;
            while (x % i == 0) {
                x /= i;
            }
        }
    }
    if (x > 1) {
        divs[cnt++] = x;
    }
    for (int g = 2;; g++) {
        bool ok = true;
        for (int i = 0; i < cnt; i++) {
            if (pow_mod_constexpr(g, (m - 1) / divs[i], m) == 1) {
                ok = false;
                break;
            }
        }
        if (ok) return g;
    }
}
template <int m> constexpr int primitive_root = primitive_root_constexpr(m);

unsigned long long floor_sum_unsigned(unsigned long long n,
                                      unsigned long long m,
                                      unsigned long long a,
                                      unsigned long long b) {
    unsigned long long ans = 0;
    while (true) {
        if (a >= m) {
            ans += n * (n - 1) / 2 * (a / m);
            a %= m;
        }
        if (b >= m) {
            ans += n * (b / m);
            b %= m;
        }

        unsigned long long y_max = a * n + b;
        if (y_max < m) break;
        n = (unsigned long long)(y_max / m);
        b = (unsigned long long)(y_max % m);
        std::swap(m, a);
    }
    return ans;
}

}  // namespace internal

}  // namespace atcoder


#include <cassert>
#include <numeric>
#include <type_traits>

namespace atcoder {

namespace internal {

#ifndef _MSC_VER
template <class T>
using is_signed_int128 =
    typename std::conditional<std::is_same<T, __int128_t>::value ||
                                  std::is_same<T, __int128>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using is_unsigned_int128 =
    typename std::conditional<std::is_same<T, __uint128_t>::value ||
                                  std::is_same<T, unsigned __int128>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using make_unsigned_int128 =
    typename std::conditional<std::is_same<T, __int128_t>::value,
                              __uint128_t,
                              unsigned __int128>;

template <class T>
using is_integral = typename std::conditional<std::is_integral<T>::value ||
                                                  is_signed_int128<T>::value ||
                                                  is_unsigned_int128<T>::value,
                                              std::true_type,
                                              std::false_type>::type;

template <class T>
using is_signed_int = typename std::conditional<(is_integral<T>::value &&
                                                 std::is_signed<T>::value) ||
                                                    is_signed_int128<T>::value,
                                                std::true_type,
                                                std::false_type>::type;

template <class T>
using is_unsigned_int =
    typename std::conditional<(is_integral<T>::value &&
                               std::is_unsigned<T>::value) ||
                                  is_unsigned_int128<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using to_unsigned = typename std::conditional<
    is_signed_int128<T>::value,
    make_unsigned_int128<T>,
    typename std::conditional<std::is_signed<T>::value,
                              std::make_unsigned<T>,
                              std::common_type<T>>::type>::type;

#else

template <class T> using is_integral = typename std::is_integral<T>;

template <class T>
using is_signed_int =
    typename std::conditional<is_integral<T>::value && std::is_signed<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using is_unsigned_int =
    typename std::conditional<is_integral<T>::value &&
                                  std::is_unsigned<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using to_unsigned = typename std::conditional<is_signed_int<T>::value,
                                              std::make_unsigned<T>,
                                              std::common_type<T>>::type;

#endif

template <class T>
using is_signed_int_t = std::enable_if_t<is_signed_int<T>::value>;

template <class T>
using is_unsigned_int_t = std::enable_if_t<is_unsigned_int<T>::value>;

template <class T> using to_unsigned_t = typename to_unsigned<T>::type;

}  // namespace internal

}  // namespace atcoder


namespace atcoder {

namespace internal {

struct modint_base {};
struct static_modint_base : modint_base {};

template <class T> using is_modint = std::is_base_of<modint_base, T>;
template <class T> using is_modint_t = std::enable_if_t<is_modint<T>::value>;

}  // namespace internal

template <int m, std::enable_if_t<(1 <= m)>* = nullptr>
struct static_modint : internal::static_modint_base {
    using mint = static_modint;

  public:
    static constexpr int mod() { return m; }
    static mint raw(int v) {
        mint x;
        x._v = v;
        return x;
    }

    static_modint() : _v(0) {}
    template <class T, internal::is_signed_int_t<T>* = nullptr>
    static_modint(T v) {
        long long x = (long long)(v % (long long)(umod()));
        if (x < 0) x += umod();
        _v = (unsigned int)(x);
    }
    template <class T, internal::is_unsigned_int_t<T>* = nullptr>
    static_modint(T v) {
        _v = (unsigned int)(v % umod());
    }

    unsigned int val() const { return _v; }

    mint& operator++() {
        _v++;
        if (_v == umod()) _v = 0;
        return *this;
    }
    mint& operator--() {
        if (_v == 0) _v = umod();
        _v--;
        return *this;
    }
    mint operator++(int) {
        mint result = *this;
        ++*this;
        return result;
    }
    mint operator--(int) {
        mint result = *this;
        --*this;
        return result;
    }

    mint& operator+=(const mint& rhs) {
        _v += rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator-=(const mint& rhs) {
        _v -= rhs._v;
        if (_v >= umod()) _v += umod();
        return *this;
    }
    mint& operator*=(const mint& rhs) {
        unsigned long long z = _v;
        z *= rhs._v;
        _v = (unsigned int)(z % umod());
        return *this;
    }
    mint& operator/=(const mint& rhs) { return *this = *this * rhs.inv(); }

    mint operator+() const { return *this; }
    mint operator-() const { return mint() - *this; }

    mint pow(long long n) const {
        assert(0 <= n);
        mint x = *this, r = 1;
        while (n) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        return r;
    }
    mint inv() const {
        if (prime) {
            assert(_v);
            return pow(umod() - 2);
        } else {
            auto eg = internal::inv_gcd(_v, m);
            assert(eg.first == 1);
            return eg.second;
        }
    }

    friend mint operator+(const mint& lhs, const mint& rhs) {
        return mint(lhs) += rhs;
    }
    friend mint operator-(const mint& lhs, const mint& rhs) {
        return mint(lhs) -= rhs;
    }
    friend mint operator*(const mint& lhs, const mint& rhs) {
        return mint(lhs) *= rhs;
    }
    friend mint operator/(const mint& lhs, const mint& rhs) {
        return mint(lhs) /= rhs;
    }
    friend bool operator==(const mint& lhs, const mint& rhs) {
        return lhs._v == rhs._v;
    }
    friend bool operator!=(const mint& lhs, const mint& rhs) {
        return lhs._v != rhs._v;
    }

  private:
    unsigned int _v;
    static constexpr unsigned int umod() { return m; }
    static constexpr bool prime = internal::is_prime<m>;
};

template <int id> struct dynamic_modint : internal::modint_base {
    using mint = dynamic_modint;

  public:
    static int mod() { return (int)(bt.umod()); }
    static void set_mod(int m) {
        assert(1 <= m);
        bt = internal::barrett(m);
    }
    static mint raw(int v) {
        mint x;
        x._v = v;
        return x;
    }

    dynamic_modint() : _v(0) {}
    template <class T, internal::is_signed_int_t<T>* = nullptr>
    dynamic_modint(T v) {
        long long x = (long long)(v % (long long)(mod()));
        if (x < 0) x += mod();
        _v = (unsigned int)(x);
    }
    template <class T, internal::is_unsigned_int_t<T>* = nullptr>
    dynamic_modint(T v) {
        _v = (unsigned int)(v % mod());
    }

    unsigned int val() const { return _v; }

    mint& operator++() {
        _v++;
        if (_v == umod()) _v = 0;
        return *this;
    }
    mint& operator--() {
        if (_v == 0) _v = umod();
        _v--;
        return *this;
    }
    mint operator++(int) {
        mint result = *this;
        ++*this;
        return result;
    }
    mint operator--(int) {
        mint result = *this;
        --*this;
        return result;
    }

    mint& operator+=(const mint& rhs) {
        _v += rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator-=(const mint& rhs) {
        _v += mod() - rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator*=(const mint& rhs) {
        _v = bt.mul(_v, rhs._v);
        return *this;
    }
    mint& operator/=(const mint& rhs) { return *this = *this * rhs.inv(); }

    mint operator+() const { return *this; }
    mint operator-() const { return mint() - *this; }

    mint pow(long long n) const {
        assert(0 <= n);
        mint x = *this, r = 1;
        while (n) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        return r;
    }
    mint inv() const {
        auto eg = internal::inv_gcd(_v, mod());
        assert(eg.first == 1);
        return eg.second;
    }

    friend mint operator+(const mint& lhs, const mint& rhs) {
        return mint(lhs) += rhs;
    }
    friend mint operator-(const mint& lhs, const mint& rhs) {
        return mint(lhs) -= rhs;
    }
    friend mint operator*(const mint& lhs, const mint& rhs) {
        return mint(lhs) *= rhs;
    }
    friend mint operator/(const mint& lhs, const mint& rhs) {
        return mint(lhs) /= rhs;
    }
    friend bool operator==(const mint& lhs, const mint& rhs) {
        return lhs._v == rhs._v;
    }
    friend bool operator!=(const mint& lhs, const mint& rhs) {
        return lhs._v != rhs._v;
    }

  private:
    unsigned int _v;
    static internal::barrett bt;
    static unsigned int umod() { return bt.umod(); }
};
template <int id> internal::barrett dynamic_modint<id>::bt(998244353);

using modint998244353 = static_modint<998244353>;
using modint1000000007 = static_modint<1000000007>;
using modint = dynamic_modint<-1>;

namespace internal {

template <class T>
using is_static_modint = std::is_base_of<internal::static_modint_base, T>;

template <class T>
using is_static_modint_t = std::enable_if_t<is_static_modint<T>::value>;

template <class> struct is_dynamic_modint : public std::false_type {};
template <int id>
struct is_dynamic_modint<dynamic_modint<id>> : public std::true_type {};

template <class T>
using is_dynamic_modint_t = std::enable_if_t<is_dynamic_modint<T>::value>;

}  // namespace internal

}  // namespace atcoder

using namespace std;

using mint = atcoder::modint998244353;

template<class Field>
struct SquareMatrix : std::vector<std::vector<Field>> {
private:
    using Self = SquareMatrix<Field>;
    using size_t = std::size_t;
    using u64 = std::uint_fast64_t;
public:
    SquareMatrix(size_t N ,Field val = 0) : std::vector<std::vector<Field>>::vector(N, std::vector<Field>(N, val)) { }

    Self operator+(const Self &rhs) const noexcept {
        return Self(*this) += rhs;
    }

    Self operator-(const Self &rhs) const noexcept {
        return Self(*this) -= rhs;
    }

    Self operator*(const Self &rhs) const noexcept {
        return Self(*this) *= rhs;
    }

    Self &operator+=(const Self &rhs) noexcept {
        size_t N = (*this).size();
        for(size_t i =  0; i < N; ++i) {
            for(size_t j = 0; j < N; ++j) {
                (*this)[i][j] += rhs[i][j];
            }
        }
        return *this;
    }

    Self &operator-=(const Self &rhs) noexcept {
        size_t N = (*this).size();
        for(size_t i =  0; i < N; ++i) {
            for(size_t j = 0; j < N; ++j) {
                (*this)[i][j] -= rhs[i][j];
            }
        }
        return *this;
    }

    Self &operator*=(const Self &rhs) noexcept {
        size_t N = (*this).size();
        Self ret(N, std::vector<mint>(N, 0));
        for(size_t i = 0; i < N; ++i) {
            for(size_t j = 0; j < N; ++j) {
                for(size_t k = 0; k < N; ++k) {
                    ret[i][j] += (*this)[i][k]*rhs[k][j];
                }
            }
        }
        return *this = ret;
    }

    Self pow(u64 n) {
        size_t N = (*this).size();
        Self res(N, std::vector<Field>(N, 0));
        for(size_t i = 0; i < N;  ++i) {
            res[i][i] = 1;
        }
        Self x = *this;
        while(n > 0) {
            if(n & 1) res *= x;
            x *= x;
            n >>= 1;
        }
        return res;
    }

    Field det() {
        Self res = *this;
        size_t N = res.size();
        Field d = 1;
        for(size_t i = 0; i < N; ++i) {
            if(res[i][i] == 0) {
                int flag = -1;
                for(size_t j = i+1; j < N; ++j) {
                    if(res[j][i] != 0) {
                        flag = j;
                        break;
                    }
                }
                if(flag < 0) {
                    return 0;
                }
                std::swap(res[i], res[flag]);
                d *= -1;
            }
            Field inv = res[i][i].inv();
            for(size_t j = i+1; j < N; ++j) {
                Field fac = res[j][i]*inv;
                for(size_t k = i; k < N; ++k) {
                    res[j][k] -= fac * res[i][k];
                }
            }
            d *= res[i][i];
        }
        return d;
    }

    static Self identity(size_t N) {
        Self res;
        for(size_t i = 0; i < N; ++i) {
            res[i][i] = 1;
        }
        return res;
    }
};

using Matrix = SquareMatrix<mint>;


mint get_number_of_spanning_tree(const vector<vector<int>>& adj, int mask) {
    vector<int> v;
    for (int i = 0; i < (int)adj.size(); ++i) {
        if ((mask >> i) & 1) {
            v.push_back(i);
        }
    }
    int n = v.size();
    if (n <= 1) return n;
    Matrix mat(n - 1);
    for (int i = 0; i < n - 1; ++i) {
        int deg = 0;
        for (int j = 0; j < n; ++j) {
            if (adj[v[i]][v[j]]) {
                ++deg;
            }
        }
        mat[i][i] = deg;
        for (int j = 0; j < n - 1; ++j) {
            if (i == j) continue;
            mat[i][j] = -adj[v[i]][v[j]];
        }
    }
    return mat.det();
}


int cnt(const vector<vector<int>>& adj, int mask1, int mask2) {
    int n = adj.size();
    int res = 0;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (adj[i][j] && ((mask1 >> i) & 1) && ((mask2 >> j) & 1)) {
                ++res;
            }
        }
    }
    return res;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(0);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> adj(n, vector<int>(n));
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        --u;
        --v;
        adj[u][v] = adj[v][u] = 1;
    }
    vector<mint> st_num(1 << n);
    for (int i = 1; i < (1 << n); ++i) {
        st_num[i] = get_number_of_spanning_tree(adj, i);
    }
    mint ans = 0;
    vector<mint> pow2(m + 1);
    pow2[0] = 1;
    for (int i = 0; i < m; ++i) {
        pow2[i + 1] = pow2[i] * 2;
    }
    vector<vector<int>> bits(1 << n);
    for (int i = 0; i < (1 << n); ++i) {
        for (int j = 0; j < n; ++j) {
            if ((i >> j) & 1) {
                bits[i].push_back(j);
            }
        }
    }
    for (int i = 1; i < (1 << n) - 1; ++i) {
        int s = ((1 << n) - 1) ^ i;
        vector<int> cnt(n);
        for (auto& j : bits[i]) {
            for (int k = 0; k < n; ++k) {
                cnt[k] += adj[j][k];
            }
        }
        int t = s;
        for (int j = 0; j < n; ++j) {
            if ((s >> j) & 1) {
                t = s ^ (1 << j);
            }
        }
        // if (st_num[i] == 0) continue;
        for (int j = t; j != 0; j = (j - 1) & t) {
            int k = s ^ j;
            int cntj = 0;
            int cntk = 0;
            for (auto& l : bits[j]) {
                cntj += cnt[l];
            }
            for (auto& l : bits[k]) {
                cntk += cnt[l];
            }
            ans += st_num[i] * st_num[j] * st_num[k] * (pow2[cntj] - 1) * (pow2[cntk] - 1);
        }
    }
    cout << (ans * 2).val() << '\n';

    return 0;
}
accuracy: 1
time_ms: 1070
memory_kb: 14956
final_rank: 10

code_snippet: #pragma GCC optimize("O3")
#include<bits/stdc++.h>

#include <cassert>
#include <numeric>
#include <type_traits>

#ifdef _MSC_VER
#include <intrin.h>
#endif


#include <utility>

#ifdef _MSC_VER
#include <intrin.h>
#endif

namespace atcoder {

namespace internal {

constexpr long long safe_mod(long long x, long long m) {
    x %= m;
    if (x < 0) x += m;
    return x;
}

struct barrett {
    unsigned int _m;
    unsigned long long im;

    explicit barrett(unsigned int m) : _m(m), im((unsigned long long)(-1) / m + 1) {}

    unsigned int umod() const { return _m; }

    unsigned int mul(unsigned int a, unsigned int b) const {

        unsigned long long z = a;
        z *= b;
#ifdef _MSC_VER
        unsigned long long x;
        _umul128(z, im, &x);
#else
        unsigned long long x =
            (unsigned long long)(((unsigned __int128)(z)*im) >> 64);
#endif
        unsigned int v = (unsigned int)(z - x * _m);
        if (_m <= v) v += _m;
        return v;
    }
};

constexpr long long pow_mod_constexpr(long long x, long long n, int m) {
    if (m == 1) return 0;
    unsigned int _m = (unsigned int)(m);
    unsigned long long r = 1;
    unsigned long long y = safe_mod(x, m);
    while (n) {
        if (n & 1) r = (r * y) % _m;
        y = (y * y) % _m;
        n >>= 1;
    }
    return r;
}

constexpr bool is_prime_constexpr(int n) {
    if (n <= 1) return false;
    if (n == 2 || n == 7 || n == 61) return true;
    if (n % 2 == 0) return false;
    long long d = n - 1;
    while (d % 2 == 0) d /= 2;
    constexpr long long bases[3] = {2, 7, 61};
    for (long long a : bases) {
        long long t = d;
        long long y = pow_mod_constexpr(a, t, n);
        while (t != n - 1 && y != 1 && y != n - 1) {
            y = y * y % n;
            t <<= 1;
        }
        if (y != n - 1 && t % 2 == 0) {
            return false;
        }
    }
    return true;
}
template <int n> constexpr bool is_prime = is_prime_constexpr(n);

constexpr std::pair<long long, long long> inv_gcd(long long a, long long b) {
    a = safe_mod(a, b);
    if (a == 0) return {b, 0};

    long long s = b, t = a;
    long long m0 = 0, m1 = 1;

    while (t) {
        long long u = s / t;
        s -= t * u;
        m0 -= m1 * u;  // |m1 * u| <= |m1| * s <= b


        auto tmp = s;
        s = t;
        t = tmp;
        tmp = m0;
        m0 = m1;
        m1 = tmp;
    }
    if (m0 < 0) m0 += b / s;
    return {s, m0};
}

constexpr int primitive_root_constexpr(int m) {
    if (m == 2) return 1;
    if (m == 167772161) return 3;
    if (m == 469762049) return 3;
    if (m == 754974721) return 11;
    if (m == 998244353) return 3;
    int divs[20] = {};
    divs[0] = 2;
    int cnt = 1;
    int x = (m - 1) / 2;
    while (x % 2 == 0) x /= 2;
    for (int i = 3; (long long)(i)*i <= x; i += 2) {
        if (x % i == 0) {
            divs[cnt++] = i;
            while (x % i == 0) {
                x /= i;
            }
        }
    }
    if (x > 1) {
        divs[cnt++] = x;
    }
    for (int g = 2;; g++) {
        bool ok = true;
        for (int i = 0; i < cnt; i++) {
            if (pow_mod_constexpr(g, (m - 1) / divs[i], m) == 1) {
                ok = false;
                break;
            }
        }
        if (ok) return g;
    }
}
template <int m> constexpr int primitive_root = primitive_root_constexpr(m);

unsigned long long floor_sum_unsigned(unsigned long long n,
                                      unsigned long long m,
                                      unsigned long long a,
                                      unsigned long long b) {
    unsigned long long ans = 0;
    while (true) {
        if (a >= m) {
            ans += n * (n - 1) / 2 * (a / m);
            a %= m;
        }
        if (b >= m) {
            ans += n * (b / m);
            b %= m;
        }

        unsigned long long y_max = a * n + b;
        if (y_max < m) break;
        n = (unsigned long long)(y_max / m);
        b = (unsigned long long)(y_max % m);
        std::swap(m, a);
    }
    return ans;
}

}  // namespace internal

}  // namespace atcoder


#include <cassert>
#include <numeric>
#include <type_traits>

namespace atcoder {

namespace internal {

#ifndef _MSC_VER
template <class T>
using is_signed_int128 =
    typename std::conditional<std::is_same<T, __int128_t>::value ||
                                  std::is_same<T, __int128>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using is_unsigned_int128 =
    typename std::conditional<std::is_same<T, __uint128_t>::value ||
                                  std::is_same<T, unsigned __int128>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using make_unsigned_int128 =
    typename std::conditional<std::is_same<T, __int128_t>::value,
                              __uint128_t,
                              unsigned __int128>;

template <class T>
using is_integral = typename std::conditional<std::is_integral<T>::value ||
                                                  is_signed_int128<T>::value ||
                                                  is_unsigned_int128<T>::value,
                                              std::true_type,
                                              std::false_type>::type;

template <class T>
using is_signed_int = typename std::conditional<(is_integral<T>::value &&
                                                 std::is_signed<T>::value) ||
                                                    is_signed_int128<T>::value,
                                                std::true_type,
                                                std::false_type>::type;

template <class T>
using is_unsigned_int =
    typename std::conditional<(is_integral<T>::value &&
                               std::is_unsigned<T>::value) ||
                                  is_unsigned_int128<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using to_unsigned = typename std::conditional<
    is_signed_int128<T>::value,
    make_unsigned_int128<T>,
    typename std::conditional<std::is_signed<T>::value,
                              std::make_unsigned<T>,
                              std::common_type<T>>::type>::type;

#else

template <class T> using is_integral = typename std::is_integral<T>;

template <class T>
using is_signed_int =
    typename std::conditional<is_integral<T>::value && std::is_signed<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using is_unsigned_int =
    typename std::conditional<is_integral<T>::value &&
                                  std::is_unsigned<T>::value,
                              std::true_type,
                              std::false_type>::type;

template <class T>
using to_unsigned = typename std::conditional<is_signed_int<T>::value,
                                              std::make_unsigned<T>,
                                              std::common_type<T>>::type;

#endif

template <class T>
using is_signed_int_t = std::enable_if_t<is_signed_int<T>::value>;

template <class T>
using is_unsigned_int_t = std::enable_if_t<is_unsigned_int<T>::value>;

template <class T> using to_unsigned_t = typename to_unsigned<T>::type;

}  // namespace internal

}  // namespace atcoder


namespace atcoder {

namespace internal {

struct modint_base {};
struct static_modint_base : modint_base {};

template <class T> using is_modint = std::is_base_of<modint_base, T>;
template <class T> using is_modint_t = std::enable_if_t<is_modint<T>::value>;

}  // namespace internal

template <int m, std::enable_if_t<(1 <= m)>* = nullptr>
struct static_modint : internal::static_modint_base {
    using mint = static_modint;

  public:
    static constexpr int mod() { return m; }
    static mint raw(int v) {
        mint x;
        x._v = v;
        return x;
    }

    static_modint() : _v(0) {}
    template <class T, internal::is_signed_int_t<T>* = nullptr>
    static_modint(T v) {
        long long x = (long long)(v % (long long)(umod()));
        if (x < 0) x += umod();
        _v = (unsigned int)(x);
    }
    template <class T, internal::is_unsigned_int_t<T>* = nullptr>
    static_modint(T v) {
        _v = (unsigned int)(v % umod());
    }

    unsigned int val() const { return _v; }

    mint& operator++() {
        _v++;
        if (_v == umod()) _v = 0;
        return *this;
    }
    mint& operator--() {
        if (_v == 0) _v = umod();
        _v--;
        return *this;
    }
    mint operator++(int) {
        mint result = *this;
        ++*this;
        return result;
    }
    mint operator--(int) {
        mint result = *this;
        --*this;
        return result;
    }

    mint& operator+=(const mint& rhs) {
        _v += rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator-=(const mint& rhs) {
        _v -= rhs._v;
        if (_v >= umod()) _v += umod();
        return *this;
    }
    mint& operator*=(const mint& rhs) {
        unsigned long long z = _v;
        z *= rhs._v;
        _v = (unsigned int)(z % umod());
        return *this;
    }
    mint& operator/=(const mint& rhs) { return *this = *this * rhs.inv(); }

    mint operator+() const { return *this; }
    mint operator-() const { return mint() - *this; }

    mint pow(long long n) const {
        assert(0 <= n);
        mint x = *this, r = 1;
        while (n) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        return r;
    }
    mint inv() const {
        if (prime) {
            assert(_v);
            return pow(umod() - 2);
        } else {
            auto eg = internal::inv_gcd(_v, m);
            assert(eg.first == 1);
            return eg.second;
        }
    }

    friend mint operator+(const mint& lhs, const mint& rhs) {
        return mint(lhs) += rhs;
    }
    friend mint operator-(const mint& lhs, const mint& rhs) {
        return mint(lhs) -= rhs;
    }
    friend mint operator*(const mint& lhs, const mint& rhs) {
        return mint(lhs) *= rhs;
    }
    friend mint operator/(const mint& lhs, const mint& rhs) {
        return mint(lhs) /= rhs;
    }
    friend bool operator==(const mint& lhs, const mint& rhs) {
        return lhs._v == rhs._v;
    }
    friend bool operator!=(const mint& lhs, const mint& rhs) {
        return lhs._v != rhs._v;
    }

  private:
    unsigned int _v;
    static constexpr unsigned int umod() { return m; }
    static constexpr bool prime = internal::is_prime<m>;
};

template <int id> struct dynamic_modint : internal::modint_base {
    using mint = dynamic_modint;

  public:
    static int mod() { return (int)(bt.umod()); }
    static void set_mod(int m) {
        assert(1 <= m);
        bt = internal::barrett(m);
    }
    static mint raw(int v) {
        mint x;
        x._v = v;
        return x;
    }

    dynamic_modint() : _v(0) {}
    template <class T, internal::is_signed_int_t<T>* = nullptr>
    dynamic_modint(T v) {
        long long x = (long long)(v % (long long)(mod()));
        if (x < 0) x += mod();
        _v = (unsigned int)(x);
    }
    template <class T, internal::is_unsigned_int_t<T>* = nullptr>
    dynamic_modint(T v) {
        _v = (unsigned int)(v % mod());
    }

    unsigned int val() const { return _v; }

    mint& operator++() {
        _v++;
        if (_v == umod()) _v = 0;
        return *this;
    }
    mint& operator--() {
        if (_v == 0) _v = umod();
        _v--;
        return *this;
    }
    mint operator++(int) {
        mint result = *this;
        ++*this;
        return result;
    }
    mint operator--(int) {
        mint result = *this;
        --*this;
        return result;
    }

    mint& operator+=(const mint& rhs) {
        _v += rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator-=(const mint& rhs) {
        _v += mod() - rhs._v;
        if (_v >= umod()) _v -= umod();
        return *this;
    }
    mint& operator*=(const mint& rhs) {
        _v = bt.mul(_v, rhs._v);
        return *this;
    }
    mint& operator/=(const mint& rhs) { return *this = *this * rhs.inv(); }

    mint operator+() const { return *this; }
    mint operator-() const { return mint() - *this; }

    mint pow(long long n) const {
        assert(0 <= n);
        mint x = *this, r = 1;
        while (n) {
            if (n & 1) r *= x;
            x *= x;
            n >>= 1;
        }
        return r;
    }
    mint inv() const {
        auto eg = internal::inv_gcd(_v, mod());
        assert(eg.first == 1);
        return eg.second;
    }

    friend mint operator+(const mint& lhs, const mint& rhs) {
        return mint(lhs) += rhs;
    }
    friend mint operator-(const mint& lhs, const mint& rhs) {
        return mint(lhs) -= rhs;
    }
    friend mint operator*(const mint& lhs, const mint& rhs) {
        return mint(lhs) *= rhs;
    }
    friend mint operator/(const mint& lhs, const mint& rhs) {
        return mint(lhs) /= rhs;
    }
    friend bool operator==(const mint& lhs, const mint& rhs) {
        return lhs._v == rhs._v;
    }
    friend bool operator!=(const mint& lhs, const mint& rhs) {
        return lhs._v != rhs._v;
    }

  private:
    unsigned int _v;
    static internal::barrett bt;
    static unsigned int umod() { return bt.umod(); }
};
template <int id> internal::barrett dynamic_modint<id>::bt(998244353);

using modint998244353 = static_modint<998244353>;
using modint1000000007 = static_modint<1000000007>;
using modint = dynamic_modint<-1>;

namespace internal {

template <class T>
using is_static_modint = std::is_base_of<internal::static_modint_base, T>;

template <class T>
using is_static_modint_t = std::enable_if_t<is_static_modint<T>::value>;

template <class> struct is_dynamic_modint : public std::false_type {};
template <int id>
struct is_dynamic_modint<dynamic_modint<id>> : public std::true_type {};

template <class T>
using is_dynamic_modint_t = std::enable_if_t<is_dynamic_modint<T>::value>;

}  // namespace internal

}  // namespace atcoder

using namespace std;

using mint = atcoder::modint998244353;

template<class Field>
struct SquareMatrix : std::vector<std::vector<Field>> {
private:
    using Self = SquareMatrix<Field>;
    using size_t = std::size_t;
    using u64 = std::uint_fast64_t;
public:
    SquareMatrix(size_t N ,Field val = 0) : std::vector<std::vector<Field>>::vector(N, std::vector<Field>(N, val)) { }

    Self operator+(const Self &rhs) const noexcept {
        return Self(*this) += rhs;
    }

    Self operator-(const Self &rhs) const noexcept {
        return Self(*this) -= rhs;
    }

    Self operator*(const Self &rhs) const noexcept {
        return Self(*this) *= rhs;
    }

    Self &operator+=(const Self &rhs) noexcept {
        size_t N = (*this).size();
        for(size_t i =  0; i < N; ++i) {
            for(size_t j = 0; j < N; ++j) {
                (*this)[i][j] += rhs[i][j];
            }
        }
        return *this;
    }

    Self &operator-=(const Self &rhs) noexcept {
        size_t N = (*this).size();
        for(size_t i =  0; i < N; ++i) {
            for(size_t j = 0; j < N; ++j) {
                (*this)[i][j] -= rhs[i][j];
            }
        }
        return *this;
    }

    Self &operator*=(const Self &rhs) noexcept {
        size_t N = (*this).size();
        Self ret(N, std::vector<mint>(N, 0));
        for(size_t i = 0; i < N; ++i) {
            for(size_t j = 0; j < N; ++j) {
                for(size_t k = 0; k < N; ++k) {
                    ret[i][j] += (*this)[i][k]*rhs[k][j];
                }
            }
        }
        return *this = ret;
    }

    Self pow(u64 n) {
        size_t N = (*this).size();
        Self res(N, std::vector<Field>(N, 0));
        for(size_t i = 0; i < N;  ++i) {
            res[i][i] = 1;
        }
        Self x = *this;
        while(n > 0) {
            if(n & 1) res *= x;
            x *= x;
            n >>= 1;
        }
        return res;
    }

    Field det() {
        Self res = *this;
        size_t N = res.size();
        Field d = 1;
        for(size_t i = 0; i < N; ++i) {
            if(res[i][i] == 0) {
                int flag = -1;
                for(size_t j = i+1; j < N; ++j) {
                    if(res[j][i] != 0) {
                        flag = j;
                        break;
                    }
                }
                if(flag < 0) {
                    return 0;
                }
                std::swap(res[i], res[flag]);
                d *= -1;
            }
            Field inv = res[i][i].inv();
            for(size_t j = i+1; j < N; ++j) {
                Field fac = res[j][i]*inv;
                for(size_t k = i; k < N; ++k) {
                    res[j][k] -= fac * res[i][k];
                }
            }
            d *= res[i][i];
        }
        return d;
    }

    static Self identity(size_t N) {
        Self res;
        for(size_t i = 0; i < N; ++i) {
            res[i][i] = 1;
        }
        return res;
    }
};

using Matrix = SquareMatrix<mint>;


mint get_number_of_spanning_tree(const vector<vector<int>>& adj, int mask) {
    vector<int> v;
    for (int i = 0; i < (int)adj.size(); ++i) {
        if ((mask >> i) & 1) {
            v.push_back(i);
        }
    }
    int n = v.size();
    if (n <= 1) return n;
    Matrix mat(n - 1);
    for (int i = 0; i < n - 1; ++i) {
        int deg = 0;
        for (int j = 0; j < n; ++j) {
            if (adj[v[i]][v[j]]) {
                ++deg;
            }
        }
        mat[i][i] = deg;
        for (int j = 0; j < n - 1; ++j) {
            if (i == j) continue;
            mat[i][j] = -adj[v[i]][v[j]];
        }
    }
    return mat.det();
}


int cnt(const vector<vector<int>>& adj, int mask1, int mask2) {
    int n = adj.size();
    int res = 0;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (adj[i][j] && ((mask1 >> i) & 1) && ((mask2 >> j) & 1)) {
                ++res;
            }
        }
    }
    return res;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(0);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> adj(n, vector<int>(n));
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        --u;
        --v;
        adj[u][v] = adj[v][u] = 1;
    }
    vector<mint> st_num(1 << n);
    for (int i = 1; i < (1 << n); ++i) {
        st_num[i] = get_number_of_spanning_tree(adj, i);
    }
    mint ans = 0;
    vector<mint> pow2(m + 1);
    pow2[0] = 1;
    for (int i = 0; i < m; ++i) {
        pow2[i + 1] = pow2[i] * 2;
    }
    vector<vector<int>> bits(1 << n);
    for (int i = 0; i < (1 << n); ++i) {
        for (int j = 0; j < n; ++j) {
            if ((i >> j) & 1) {
                bits[i].push_back(j);
            }
        }
    }
    for (int i = 1; i < (1 << n) - 1; ++i) {
        int s = ((1 << n) - 1) ^ i;
        vector<int> cnt(n);
        for (auto& j : bits[i]) {
            for (int k = 0; k < n; ++k) {
                cnt[k] += adj[j][k];
            }
        }
        // if (st_num[i] == 0) continue;
        for (int j = (s - 1) & s; j != 0; j = (j - 1) & s) {
            int k = s ^ j;
            int cntj = 0;
            int cntk = 0;
            for (auto& l : bits[j]) {
                cntj += cnt[l];
            }
            for (auto& l : bits[k]) {
                cntk += cnt[l];
            }
            ans += st_num[i] * st_num[j] * st_num[k] * (pow2[cntj] - 1) * (pow2[cntk] - 1);
        }
    }
    cout << ans.val() << '\n';

    return 0;
}
accuracy: 1
time_ms: 1840
memory_kb: 14840
final_rank: 15

code_snippet: #include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define inf 1000000007
#define MP make_pair
#define MT make_tuple
#define PB push_back
#define fi first
#define se second
#define rep(i,n) for(int i = 0; i < (int)(n); ++i)
#define rrep(i,n) for(int i = (int)n-1; i >= 0; --i)
#define srep(i,a,b) for(int i = (int)a; i < (int)(b); ++i)
#define all(x) (x).begin(),(x).end()
#define SUM(v) accumulate(all(v), 0LL)
#define MIN(v) *min_element(all(v))
#define MAX(v) *max_element(all(v))
#define lb(c, x) distance((c).begin(), lower_bound(all(c), (x)))
#define ub(c, x) distance((c).begin(), upper_bound(all(c), (x)))
#define UNIQUE(x) sort(all(x)), x.erase(unique(all(x)), x.end())
#define SZ(c) (int)(c).size()
template<typename T>
ostream& operator << (ostream& os, vector<T>& vec) {
    os << "{";
    for (int i = 0; i<(int)vec.size(); i++) {
        os << vec[i] << (i + 1 == (int)vec.size() ? "" : ", ");
    }
    os << "}";
    return os;
}
// pair出力
template<typename T, typename U>
ostream& operator << (ostream& os, pair<T, U> pair_var) {
    os << "(" << pair_var.first << ", " << pair_var.second << ")";
    return os;
}
// map出力
template<typename T, typename U>
ostream& operator << (ostream& os, map<T, U>& map_var) {
    os << "{";
    for(auto itr = map_var.begin(); itr != map_var.end(); itr++){
        os << "(" << itr->first << ", " << itr->second << ")";
        itr++;
        if(itr != map_var.end()) os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
// set 出力
template<typename T>
ostream& operator << (ostream& os, set<T>& set_var) {
    os << "{";
    for(auto itr = set_var.begin(); itr != set_var.end(); itr++){
        os << (*itr);
        ++itr;
        if(itr != set_var.end()) os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
#define overload2(_1, _2, name, ...) name
#define vec(type, name, ...) vector<type> name(__VA_ARGS__)
#define VEC(type, name, size)                                                                                                                                  \
    vector<type> name(size);                                                                                                                                   \
    IN(name)
#define vv(type, name, h, ...) vector<vector<type>> name(h, vector<type>(__VA_ARGS__))
#define VV(type, name, h, w)                                                                                                                                   \
    vector<vector<type>> name(h, vector<type>(w));                                                                                                             \
    IN(name)
#define vvv(type, name, h, w, ...) vector<vector<vector<type>>> name(h, vector<vector<type>>(w, vector<type>(__VA_ARGS__)))
#define vvvv(type, name, a, b, c, ...)                                                                                                                         \
    vector<vector<vector<vector<type>>>> name(a, vector<vector<vector<type>>>(b, vector<vector<type>>(c, vector<type>(__VA_ARGS__))))
#define INT(...)                                                                                                                                               \
    int __VA_ARGS__;                                                                                                                                           \
    IN(__VA_ARGS__)
#define LL(...)                                                                                                                                                \
    ll __VA_ARGS__;                                                                                                                                            \
    IN(__VA_ARGS__)
#define STR(...)                                                                                                                                               \
    string __VA_ARGS__;                                                                                                                                        \
    IN(__VA_ARGS__)
#define CHR(...)                                                                                                                                               \
    char __VA_ARGS__;                                                                                                                                          \
    IN(__VA_ARGS__)
#define DBL(...)                                                                                                                                               \
    double __VA_ARGS__;                                                                                                                                        \
    IN(__VA_ARGS__)
int scan() { return getchar(); }
void scan(int &a) { cin >> a; }
void scan(long long &a) { cin >> a; }
void scan(char &a) { cin >> a; }
void scan(double &a) { cin >> a; }
void scan(string &a) { cin >> a; }
template <class T, class S> void scan(pair<T, S> &p) { scan(p.first), scan(p.second); }
template <class T> void scan(vector<T> &);
template <class T> void scan(vector<T> &a) {
    for(auto &i : a) scan(i);
}
template <class T> void scan(T &a) { cin >> a; }
void IN() {}
template <class Head, class... Tail> void IN(Head &head, Tail &...tail) {
    scan(head);
    IN(tail...);
}
const string YESNO[2] = {"NO", "YES"};
const string YesNo[2] = {"No", "Yes"};
const string yesno[2] = {"no", "yes"};
void YES(bool t = 1) { cout << YESNO[t] << endl; }
void NO(bool t = 1) { YES(!t); }
void Yes(bool t = 1) { cout << YesNo[t] << endl; }
void No(bool t = 1) { Yes(!t); }
void yes(bool t = 1) { cout << yesno[t] << endl; }
void no(bool t = 1) { yes(!t); }
#ifdef LOCAL
void debug_out() { cerr << endl; }
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
    cerr << " " << H;
    debug_out(T...);
}
#define dbg(...) \
    cerr << __LINE__ << " [" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#define dump(x) cerr << __LINE__ << " " << #x << " = " << (x) << endl
#else
#define dbg(...) (void(0))
#define dump(x) (void(0))
#endif
template<typename A, size_t N, typename T>
void Fill(A (&array)[N], const T &val){
    std::fill( (T*)array, (T*)(array+N), val );
}
template <class T> T ceil(T x, T y) {assert(y >= 1);return (x > 0 ? (x + y - 1) / y : x / y);}
template <class T> T floor(T x, T y) {assert(y >= 1);return (x > 0 ? x / y : (x + y - 1) / y);}
vector<int> iota(int n) {vector<int> a(n);iota(all(a), 0);return a;}
template <class T> T POW(T x, int n) {T res = 1;for(; n; n >>= 1, x *= x){if(n & 1) res *= x;}return res;}
ll pow2(int i) { return 1LL << i; }
int topbit(signed t) { return t == 0 ? -1 : 31 - __builtin_clz(t); }
int topbit(ll t) { return t == 0 ? -1 : 63 - __builtin_clzll(t); }
int lowbit(signed a) { return a == 0 ? 32 : __builtin_ctz(a); }
int lowbit(ll a) { return a == 0 ? 64 : __builtin_ctzll(a); }
// int allbit(int n) { return (1 << n) - 1; }
ll allbit(ll n) { return (1LL << n) - 1; }
int popcount(signed t) { return __builtin_popcount(t); }
int popcount(ll t) { return __builtin_popcountll(t); }
bool ispow2(int i) { return i && (i & -i) == i; }


template <class S> void fold_in(vector<S> &v) {}
template <typename Head, typename... Tail, class S> void fold_in(vector<S> &v, Head &&a, Tail &&...tail) {
    for(auto e : a) v.emplace_back(e);
    fold_in(v, tail...);
}
template <class S> void renumber(vector<S> &v) {}
template <typename Head, typename... Tail, class S> void renumber(vector<S> &v, Head &&a, Tail &&...tail) {
    for(auto &&e : a) e = lb(v, e);
    renumber(v, tail...);
}
template <class S, class... Args> void zip(vector<S> &head, Args &&...args) {
    vector<S> v;
    fold_in(v, head, args...);
    sort(all(v)), v.erase(unique(all(v)), v.end());
    renumber(v, head, args...);
}
template<class T> inline bool chmax(T &a, T b){
    if(a<b){
        a = b;
        return true;
    }
    return false;
}
template<class T> inline bool chmin(T &a, T b){
    if(a>b){
        a = b;
        return true;
    }
    return false;
}


template <unsigned int mod>
class ModInt {
private:
    unsigned int v;
    static unsigned int norm(const unsigned int& x){ return x < mod ? x : x - mod; }
    static ModInt make(const unsigned int& x){ ModInt m; return m.v = x, m; }
    static ModInt inv(const ModInt& x){ return make(inverse(x.v, mod)); }
    static unsigned int inverse(int a, int m){
        int u[] = {a, 1, 0}, v[] = {m, 0, 1}, t;
        while(*v){
            t = *u / *v;
            swap(u[0] -= t * v[0], v[0]), swap(u[1] -= t * v[1], v[1]), swap(u[2] -= t * v[2], v[2]);
        }
        return (u[1] % m + m) % m;
    }

public:
    ModInt() : v{0}{}
    ModInt(const long long val) : v{norm(val % mod + mod)} {}
    ModInt(const ModInt<mod>& n) : v{n()} {}
    explicit operator bool() const noexcept { return v != 0; }
    bool operator!() const noexcept { return !static_cast<bool>(*this); }
    ModInt& operator=(const ModInt& n){ return v = n(), (*this); }
    ModInt& operator=(const long long val){ return v = norm(val % mod + mod), (*this); }
    ModInt operator+() const { return *this; }
    ModInt operator-() const { return v == 0 ? make(0) : make(mod - v); }
    ModInt operator+(const ModInt& val) const { return make(norm(v + val())); }
    ModInt operator-(const ModInt& val) const { return make(norm(v + mod - val())); }
    ModInt operator*(const ModInt& val) const { return make((long long)v * val() % mod); }
    ModInt operator/(const ModInt& val) const { return *this * inv(val); }
    ModInt& operator+=(const ModInt& val){ return *this = *this + val; }
    ModInt& operator-=(const ModInt& val){ return *this = *this - val; }
    ModInt& operator*=(const ModInt& val){ return *this = *this * val; }
    ModInt& operator/=(const ModInt& val){ return *this = *this / val; }
    ModInt operator+(const long long val) const { return ModInt{v + val}; }
    ModInt operator-(const long long val) const { return ModInt{v - val}; }
    ModInt operator*(const long long val) const { return ModInt{(long long)v * (val % mod)}; }
    ModInt operator/(const long long val) const { return ModInt{(long long)v * inv(val)}; }
    ModInt& operator+=(const long long val){ return *this = *this + val; }
    ModInt& operator-=(const long long val){ return *this = *this - val; }
    ModInt& operator*=(const long long val){ return *this = *this * val; }
    ModInt& operator/=(const long long val){ return *this = *this / val; }
    bool operator==(const ModInt& val) const { return v == val.v; }
    bool operator!=(const ModInt& val) const { return !(*this == val); }
    bool operator==(const long long val) const { return v == norm(val % mod + mod); }
    bool operator!=(const long long val) const { return !(*this == val); }
    unsigned int operator()() const { return v; }
    friend ModInt operator+(const long long val, const ModInt& n) { return n + val; }
    friend ModInt operator-(const long long val, const ModInt& n) { return ModInt{val - n()}; }
    friend ModInt operator*(const long long val, const ModInt& n) { return n * val; }
    friend ModInt operator/(const long long val, const ModInt& n) { return ModInt{val} / n; }
    friend bool operator==(const long long val, const ModInt& n) { return n == val; }
    friend bool operator!=(const long long val, const ModInt& n) { return !(val == n); }
    friend istream& operator>>(istream& is, ModInt& n){
        unsigned int v;
        return is >> v, n = v, is;
    }
    friend ostream& operator<<(ostream& os, const ModInt& n){ return (os << n()); }
    friend ModInt mod_pow(ModInt x, long long n){
        ModInt ans = 1;
        while(n){
            if(n & 1) ans *= x;
            x *= x, n >>= 1;
        }
        return ans;
    }
};

#define MOD 998244353
using mod = ModInt<MOD>;

#define MAX_N 200000
mod inv[MAX_N],fac[MAX_N],finv[MAX_N];

void make()
{
    fac[0] = fac[1] = 1;
    finv[0] = finv[1] = 1;
    inv[1] = 1;
    for(int i=2;i<MAX_N;i++){
        inv[i] = MOD - inv[MOD % i] * (MOD / i);
        fac[i] = fac[i-1] * i;
        finv[i] = finv[i-1] * inv[i];
    }
}

mod comb(int a, int b)
{
    if(a<b) return 0;
    return fac[a] * finv[b] * finv[a-b];
}

 
template<typename T> class mat : public vector<vector<T> > {
private:
    int r, c;    //行,列
public:
    inline int row() const {
        return r;
    }
    inline int column() const {
        return c;
    }
    mat(int n, int m, T val = 0){
        r = n, c = m;
        for(int i = 0; i < n; i++){
            this->push_back(vector<T>(m, val));
        }
    }
    mat operator+(const mat& another) const {
        if(r != another.r && c != another.c){
            cout << "足し算失敗(サイズ不一致)" << endl;
            exit(1);
        }
        mat<T> X(r, c);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                X[i][j] = (*this)[i][j] + another[i][j];
            }
        }
        return X;
    }
    mat operator+(const T val) const {
        mat<T> X(r, c);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                X[i][j] = (*this)[i][j] + val;
            }
        }
        return X;
    }
    mat operator-(const mat& another) const {
        if(r != another.r && c != another.c){
            cout << "引き算失敗(サイズ不一致)" << endl;
            exit(1);
        }
        mat<T> X(r, c);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                X[i][j] = (*this)[i][j] - another[i][j];
            }
        }
        return X;
    }
    mat operator-(const T val) const {
        mat<T> X(r, c);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                X[i][j] = (*this)[i][j] - val;
            }
        }
        return X;
    }
    vector<T> operator*(const vector<T>& another) const {
        if(c != (int)another.size()){
            cout << "掛け算失敗(サイズ不一致)" << endl;
            exit(1);
        }
        vector<T> v(r,0);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                v[i] += (*this)[i][j] * another[j];
            }
        }
        return v;
    }
    mat operator*(const mat& another) const {
        if(c != another.r){
            cout << "掛け算失敗(サイズ不一致)" << endl;
            exit(1);
        }
        mat<T> X(r, another.c);
        for(int i = 0; i < r; i++){
            for(int k = 0; k < c; k++){
                for(int j = 0; j < (another.c); j++){
                    X[i][j] += (*this)[i][k] * another[k][j];
                }
            }
        }
        return X;
    }
    mat operator-() const {
        mat<T> X(r, c);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                X[i][j] = -(*this)[i][j];
            }
        }
        return X;
    }
    int rank() const {
        int res = 0;
        mat B(r, c);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                B[i][j] = (*this)[i][j];
            }
        }
        for(int i = 0; i < c; i++){
            if(res == r) return res;
            int pivot = res;
            for(int j = res; j < r; j++){
                if(B[j][i]){
                    pivot = j;
                    break;
                }
            }
            if(!B[pivot][i]) continue;
            swap(B[pivot], B[res]);
            const T d = (T)1 / B[res][i];
            for(int j = i + 1; j < c; j++){
                B[res][j] *= d;
            }
            for(int j = res + 1; j < r; j++){
                if(!B[j][i]) continue;
                for(int k = i + 1; k < c; k++){
                    B[j][k] -= B[res][k] * B[j][i];
                }
            }
            ++res;
        }
        return res;
    }
    T det() const {
        if(r != c){
            cout << "正方行列でない(行列式定義不可)" << endl;
            exit(1);
        }
        T ans = 1;
        mat B(r, r);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                B[i][j] = (*this)[i][j];
            }
        }
        for(int i = 0; i < c; i++){
            int pivot = i;
            for(int j = i; j < r; j++){
                if(B[j][i]){
                    pivot = j;
                    break;
                }
            }
            if(!B[pivot][i]) return (T)0;
            if(pivot != i) swap(B[i], B[pivot]), ans = -ans;
            ans *= B[i][i];
            const T d = (T)1 / B[i][i];
            for(int j = i + 1; j < c; j++){
                B[i][j] *= d;
            }
            for(int j = i + 1; j < r; j++){
                if(!B[j][i]) continue;
                for(int k = i + 1; k < c; k++){
                    B[j][k] -= B[i][k] * B[j][i];
                }
            }
        }
        return ans;
    }
    mat inverse() const {
        if(r != c){
            cout << "正方行列でない(逆行列定義不可)" << endl;
            exit(1);
        }
        mat B(r, 2*r);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < r; j++){
                B[i][j] = (*this)[i][j];
            }
        }
        for(int i = 0; i < r; i++){
            B[i][r+i] = 1;
        }
        for(int i = 0; i < r; i++){
            int pivot = i;
            for(int j = i; j < r; j++){
                if(B[j][i]){
                    pivot = j;
                    break;
                }
            }
            if(!B[pivot][i]){
                cout << "正則でない" << endl;
                exit(1);
            }
            swap(B[i], B[pivot]);
            const T d = (T)1 / B[i][i];
            for(int j = i + 1; j < 2*r; j++){
                B[i][j] *= d;
            }
            for(int j = 0; j < r; j++){
                if(j == i || !B[j][i]) continue;
                for(int k = i + 1; k < 2*r; k++){
                    B[j][k] -= B[i][k] * B[j][i];
                }
            }
        }
        mat res(r, r);
        for(int i = 0; i < r; i++){
            for(int j = 0; j < r; j++){
                res[i][j] = B[i][r+j];
            }
        }
        return res;
    }
    inline void print() const {
        for(int i = 0; i < r; i++){
            for(int j = 0; j < (c-1); j++){
                cout << (*this)[i][j] << ",";
            }
            cout << (*this)[i][c-1] << endl;
        }
    }
};
 
bool g[18][18];
int f[1<<17];
mod two[1000];
mod x[1<<17];
int main(){
    two[0] = 1;
    rep(i,999){
        two[i+1] = two[i]*2;
    }
    INT(n,m);
    rep(i,m){
        INT(a,b);
        if(a==b)continue;
        a--;b--;
        g[a][b] = 1;
        g[b][a] = 1;
        f[(1<<a)^(1<<b)]++;
    }
    rep(i,n) rep(j,1<<n) if (j&(1<<i)) f[j]+=f[j^(1<<i)];
    rep(bits,1<<n){
        if(bits==0)continue;
        int C = popcount(bits);
        if(C-1 > f[bits])continue;
        vector<int> p;
        rep(i,n){
            if((bits>>i)&1){
                p.push_back(i);
            }
        }
        mat<mod> mat(C-1,C-1);
        rep(i,C){
            rep(j,i){
                if(g[p[i]][p[j]]){
                    if(i==0&&j==0){

                    }else if(i==0){
                        mat[j-1][j-1]+=1;
                    }else if(j==0){
                        mat[i-1][i-1]+=1;
                    }else{
                        mat[i-1][i-1]+=1;
                        mat[j-1][j-1]+=1;
                        mat[i-1][j-1]-=1;
                        mat[j-1][i-1]-=1;
                    }
                }
            }
        }
        x[bits] = mat.det();
    }
    int ab = allbit(n);
    mod res = 0;
    rep(B,1<<n){
        if(B==0)continue;
        if(B==ab)continue;
        int CC = ab^B;
        for(int C= CC; ; C = (C-1) & CC) {
            if (!C) break;
            if(C==CC)continue;
            int D = (ab^B^C);
            res += (two[f[B^D]-f[B]-f[D]]-1)*(two[f[C^D]-f[C]-f[D]]-1) * x[B]*x[C]*x[D];
            
        }
    }
    cout << res << endl;
    return 0;
}
accuracy: 1
time_ms: 1270
memory_kb: 6808
final_rank: 7