标签:
题意:
一个无向图,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;
}
标签:
原文地址:http://www.cnblogs.com/AlpcFlagship/p/4675921.html