1. 程式人生 > >HDU 5713 K個聯通塊 狀壓dp列舉子集 (2016百度之星複賽)

HDU 5713 K個聯通塊 狀壓dp列舉子集 (2016百度之星複賽)

題意

眾所周知,度度熊喜歡圖,尤其是聯通的圖。

今天,它在圖上又玩出了新花樣,新高度。有一張無重邊的無向圖, 求有多少個邊集,使得刪掉邊集裡的邊後,圖裡恰好有K個連通塊。 

推薦題目:Codeforces #332 div 2 E Nuts (599E) 狀壓+樹形+計數

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
#include <iostream>
#include <map>
#define pii pair<int,int>
#define mp(a,b) make_pair<a,b>
#define xx first
#define yy second
#define mem(a) memset( a, 0, sizeof(a) )
using namespace std;
typedef long long ll;

ll dp[20][1<<15], f[1<<15], g[1<<15], f2[1000];
int fa[20], vis[1<<15];
int w[1<<15], n, m, k;
const ll mod = 1e9+9;

/*
dp[i][st]存的是現在有i個連通塊,加入的點的情況是st
f[st]存的是這個連通塊有多少個作為連通塊的形態數(就是這個連通塊包含的邊怎麼個刪法使他還是個連通塊,有多少種情況)
g[st]輔助陣列
抓住  第一個點一定屬於一個連通分量(所以通俗來說列舉子集時只需要列舉一半即可)

*/
int root( int x )
{
    if( fa[x] == x ) return x;
    else return root( fa[x] );
}

void merge( int a, int b )
{
    if( root(a) == root(b) ) return ;
    int pa, pb;
    pa = root(a);
    pb = root(b);
    fa[pa] = pb;
    return ;
}

int lowbit( int x )
{
   return x&-x;
}


void solve()
{
    int i, j, st;
    cin >> n >> m >> k;
    mem(f);mem(g);mem(vis);
    for( i = 0; i < n; i ++ )fa[i] = i;
    for( i = 1; i <= m; i ++ ){
        int x, y;
        scanf("%d %d", &x, &y);
        x --; y -- ;
        for( j = 0; j < (1<<n); j ++ ){
            if( ( j&(1<<x) ) && ( j&(1<<y) ) ){
                f[j] ++;
            }
        }
        merge( x, y );
    }// 用並查集判連通
    for( st = 1; st < (1<<n); st ++ ){
        int p = lowbit(st), to = -1, fail = 0;
        for( i = 0; i < n; i ++ ){
            if( st & ( 1 << i ) ){
                if( to == -1 ){
                    to = root(i);
                }
                else{
                    if( root(i) != to ){
                        fail = 1;
                        break;
                    }
                }
            }
        }
        if( fail ){
            continue;
        }
        else vis[st] = 1;
        f[st] = f2[f[st]];
        g[st] = f[st];
        for( j = st; j ; j = (j-1) & st ){
            if( j == st ) continue;
            if( !vis[j] ) continue;
            if( j & p ){
                f[st] -= ((ll)f[j]*g[j^st])%mod;
                f[st] %= mod;
            }
        }
    }// vis陣列判這個子集本身是否連通,不聯通f g = 0(可以不用判斷)
    memset( dp, 0, sizeof(dp) );
    dp[0][0] = 1;
    for( i = 1; i <= k; i ++ ){
        for( st = 1; st < (1<<n); st ++ ){
            int p = lowbit(st), sub;
            for( sub = st; sub; sub = ( sub - 1 )&st ){
                if( !vis[sub] ) continue;
                if( p & sub ){
                    dp[i][st] += ( (ll)dp[i-1][sub^st]*f[sub] )%mod;
                    dp[i][st] %= mod;
                }
            }
        }
    }
    ll ans = (dp[k][(1<<n)-1]+mod)%mod;
    printf("%lld\n", ans);
}

void init()
{
    f2[0] = 1;
    for( ll i = 1; i <= 999; i ++ ){
        f2[i] = f2[i-1]*2%mod;
    }
}

int main()
{
    int cas = 1, T;
    init();
    cin >> T;
    while( T -- ){
        printf("Case #%d:\n", cas ++);
        solve();
    }
}