LINKc++
首先在加入幾個點以後全部的點都只有三種狀態spa
一個是在獨立集中,一個是和獨立集聯通,還有一個是沒有被訪問過code
而後前兩個狀態是能夠壓縮起來的get
由於咱們只須要記錄下當前獨立集大小和是否被訪問過,而後每次加點咱們直接枚舉加入獨立集中的點而後周圍聯通的點均可以一塊兒訪問,只要保證當前枚舉的點沒有被訪問過就能夠了input
由於這樣選出來的當前的點必定是否是獨立集中的且不和獨立集聯通的it
而後每次由於加入了不少個點,咱們設\(w_i\)表示和i聯通(包括i)的全部點的集合io
而後就能夠用排列數算了,只須要保證當前選出來的加入獨立集的點在全部其餘點以前算就能夠了ast
因此是\(dp_{i+1,s|w_{j}}+=dp_{i,s}*P_{n-cnt[s]-1}^{cnt[w_j\oplus(w_j\&s)]-1}\)function
#include<bits/stdc++.h> using namespace std; const int Mod = 998244353; const int N = 21; int n, m, w[N]; int fac[N], inv[N], cnt[1 << N]; int dp[N][1 << N]; int main() { #ifdef dream_maker freopen("input.txt", "r", stdin); #endif function<int(int a, int b)> add = [&](int a, int b) { return (a += b) >= Mod ? a - Mod : a; }; function<int(int a, int b)> sub = [&](int a, int b) { return (a -= b) < 0 ? a + Mod : a; }; function<int(int a, int b)> mul = [&](int a, int b) { return (long long) a * b % Mod; }; function<int(int a, int b)> fast_pow = [&](int a, int b) { int res = 1; for (; b; b >>= 1, a = mul(a, a)) if (b & 1) res = mul(res, a); return res; }; function<int(int a, int b)> P = [&](int a, int b) { return (a < b) ? 0 : mul(fac[a], inv[a - b]); }; scanf("%d %d", &n, &m); int up = (1 << n) - 1; for (int i = 1; i <= n; i++) w[i] = 1 << (i - 1); for (int i = 1; i <= m; i++) { int u, v; scanf("%d %d", &u, &v); w[u] |= 1 << (v - 1); w[v] |= 1 << (u - 1); } inv[0] = fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = mul(fac[i - 1], i); inv[n] = fast_pow(fac[n], Mod - 2); for (int i = n - 1; i >= 1; i--) inv[i] = mul(inv[i + 1], i + 1); for (int i = 1; i <= up; i++) { for (int j = 1; j <= n; j++) { cnt[i] += (i >> (j - 1)) & 1; } } dp[0][0] = 1; for (int i = 1; i <= n; i++) { for (int s = 0; s <= up; s++) if (dp[i - 1][s]) { for (int j = 1; j <= n; j++) if (!((s >> (j - 1)) & 1)) { dp[i][s | w[j]] = add(dp[i][s | w[j]], mul(dp[i - 1][s], P(n - cnt[s] - 1, cnt[w[j] ^ (w[j] & s)] - 1))); } } } for (int i = n; i >= 1; i--) if (dp[i][up]) { printf("%d", mul(dp[i][up], inv[n])); break; } return 0; }