標籤:
題意:
一個無向圖,n個點,m條邊,要求刪掉m-n條邊使得圖任然連通,問方案數。
思路:
留下n條邊,相當於一個樹+一條邊,這樣就有一個唯一的環。
枚舉這個環並看成一個點,就相當於做產生樹計數
1、找環
s是一個二進位狀態,u為s中編號最小的點
dp[s][v] 以u為起點,經過s中所有的點,並以u結尾的路徑有多少條
這樣的環如果u,v有直接的邊相連,再加上dp[s][v]裡的路徑,就可以得到若干個環
每個環順時針被算一次,逆時針被算一次,所以要除以2
2、產生樹計數
構造基爾霍夫矩陣,環中的點看成一個點
這樣可能會有重邊,必須要保留,基爾霍夫矩陣可以計算重邊的情況
高斯消元的時候不是實數 要模數
#include <cstdio>#include <cstring>#include <vector>using namespace std;typedef long long ll;const int N = 17;const int S = 1<<N;const int mod = 998244353;ll dp[S+2][N+2], num[S+2];int n, m, lim;int g[N+5][N+5], id[N+5];ll f[N+5][N+5];int t[N+5], st[S+2];vector<int> ch[S+2];inline void add(ll& x, ll y){ x += y; if (x >= mod) x -= mod; if (x < 0) x += mod;}inline ll gcd(ll p, ll q){ ll r; while (q){ r = p%q; p = q; q = r; } return p;}inline ll fpow(ll x, int n){ ll ret = 1; while (n){ if (n&1) ret = ret*x%mod; x = x*x%mod; n >>= 1; } return ret;}inline void prepare(){ lim = 1<<N; for (int i=1; i<=N; i++) t[i] = 1<<i-1; for (int s=0; s<lim; s++){ ch[s].clear(); for (int i=1; i<=N; i++) if (s&t[i]){ if (ch[s].size() == 0) st[s] = i; ch[s].push_back(i); } }}inline void circle(){ for (int s=0; s<lim; s++) for (int i=0; i<ch[s].size(); i++){ int u = ch[s][i]; if (!dp[s][u]) continue; for (int v=1; v<=n; v++) if (v>st[s] && (!(s&t[v])) && g[u][v]){ dp[s|t[v]][v] += dp[s][u]; } } for (int s=0; s<lim; s++) if(ch[s].size()>=3){ int u = st[s]; for (int i=0; i<ch[s].size(); i++){ int v = ch[s][i]; if (v==u || !dp[s][v] || !g[u][v]) continue; num[s] += dp[s][v]; } num[s] >>= 1; num[s] %= mod; }}ll gauss(ll a[][N+5], int n){ for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) a[i][j] = a[i][j]%mod; ll ret = 1; for (int i=2; i<=n; i++){ for (int j=i+1; j<=n ;j++) while (a[j][i]){ // 類似與輾轉相除的消元 ll t = a[i][i]/a[j][i]; for (int k=i; k<=n; k++) add(a[i][k], -t*a[j][k]%mod); for (int k=i; k<=n; k++) swap(a[i][k], a[j][k]); ret = -ret; } if (!a[i][i]) return 0; ret = ret*a[i][i]%mod; } return (ret+mod)%mod;// for (int i=1; i<=n; i++)// for (int j=1; j<=n; j++){// if (a[i][j]<0) a[i][j] += mod;// }// ll ret = 1, val = 1;// for (int i=1; i<n; i++){// if (a[i][i]<0){// ret = -ret;// for (int j=i; j<=n; j++) a[i][j] = -a[i][j];// }// for (int j=i+1; j<n; j++) if (a[j][i]){// if (a[j][i]<0){// ret = -ret;// for (int k=i; k<=n; k++) a[j][k] = -a[j][k];// }// ll div = gcd(a[i][i], a[j][i]);// ll tmp = a[i][i]/div;// ll num = a[j][i]/div;// val = val*tmp%mod;// for (int k=i; k<=n; k++)// a[j][k] = (a[j][k]*tmp-a[i][k]*num+mod)%mod;// }// if (a[i][i] == 0) return 0;// ret = ret*a[i][i]%mod;// }// if (val!=1) ret = ret*fpow(val, mod-2)%mod;// return (ret+mod)%mod;}inline void solve(){ ll ans = 0; for (int s=0; s<lim; s++) if (num[s]){ memset(id, 0, sizeof(id)); int sign = 1; for (int i=1; i<=n; i++){ if (s&t[i]) id[i] = 1; // s中的點縮成編號為1的新點 else id[i] = ++sign; } memset(f, 0, sizeof(f)); for (int i=1; i<=n; i++) for (int j=i+1; j<=n; j++) if(g[i][j] && id[i]!=id[j]){ f[id[i]][id[i]] ++; // 對角上度數+1 f[id[j]][id[j]] ++; f[id[i]][id[j]] --; // id[i]與id[j]之間多一條邊 f[id[j]][id[i]] --; } ll tmp = gauss(f, sign); add(ans, num[s]*tmp%mod); // num[s]表示s可以構造出環的個數 // tmp是s縮成點後的產生樹數目} printf("%I64d\n", ans);}int main(){freopen("1005.in", "r", stdin);freopen("1005.out", "w", stdout); prepare(); while (scanf("%d %d", &n, &m) != EOF){ lim = 1<<n; memset(dp, 0, sizeof(dp)); memset(g, 0, sizeof(g)); int a, b, c; for (int i=1; i<=m; i++){ scanf("%d %d", &a, &b); g[a][b] = g[b][a] = 1; c = max(a, b); dp[t[a]|t[b]][c] = 1; } memset(num, 0, sizeof(num)); circle(); solve(); } return 0;}
hdu5304 基環 外向樹