hdu5304 基环 外向树

题意:

一个无向图,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;
}

  

 

 

posted @ 2015-07-25 14:53  ALPC_Flagship  阅读(896)  评论(0编辑  收藏  举报