[HDU5713]K个联通块

本文解析了HDU5713题目,即在一个无向图中求解有多少种边集方案,使得删去这些边后,图中恰好形成k个连通块。采用动态规划方法,通过计算特定点集下图的连通性状态来解决问题。

[HDU5713]K个联通块

题目大意:

有一张\(n(n\le14)\)个点,\(m\)条边无重边的无向图,求有多少个边集,使得删掉边集里的边后,图里恰好有\(k\)个连通块。

思路:

一个显然的动态规划是,\(f_{s,i}\)表示点集为\(s\),分成\(i\)个连通块的方案数。

转移什么的都很显然,关键是如何求\(f_{s,1}\)。(万事开头难!)

\(f_{s,1}\)的含义是删去\(s\)中若干条边使得新图仍然连通的方案数。我们可以将其转化为任意删边的方案数-删边使得该图不连通的方案数。

而后者就相对好求。

源代码:

#include<cstdio>
#include<cctype>
#include<cstring>
#include<numeric>
#include<algorithm>
inline int getint() {
    register char ch;
    while(!isdigit(ch=getchar()));
    register int x=ch^'0';
    while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
    return x;
}
typedef long long int64;
const int N=14,M=105,mod=1e9+9;
struct Edge {
    int u,v;
};
Edge e[M];
int f[1<<N][N+1],cnt[1<<N];
inline int power(int a,int k) {
    int ret=1;
    for(;k;k>>=1) {
        if(k&1) ret=(int64)ret*a%mod;
        a=(int64)a*a%mod;
    }
    return ret;
}
inline int inv(const int &x) {
    return power(x,mod-2);
}
inline int lowbit(const int &x) {
    return x&-x;
}
int main() {
    const int T=getint();
    for(register int i=1;i<=T;i++) {
        memset(f,0,sizeof f);
        memset(cnt,0,sizeof cnt);
        const int n=getint(),m=getint(),k=getint();
        int tmp=0;
        for(register int i=0;i<m;i++) {
            e[i]=(Edge){getint()-1,getint()-1};
            if(e[i].u==e[i].v) tmp++;
        }
        for(register int i=0;i<n;i++) f[1<<i][1]=1;
        for(register int s=0;s<1<<n;s++) {
            if(__builtin_popcount(s)<=1) continue;
            for(register int i=0;i<n;i++) {
                if((s>>i)&1) {
                    for(register int j=0;j<m;j++) {
                        const int &u=e[j].u,&v=e[j].v;
                        if(u==v) continue;
                        if(i==u&&((s>>v)&1)) cnt[s]++;
                        if(i==v&&((s>>u)&1)) cnt[s]++;
                    }
                }
            }
            cnt[s]>>=1;
            const int v=s^lowbit(s);
            for(register int t=(v-1)&v;;t=(t-1)&v) {
                (f[s][1]+=(int64)f[t^lowbit(s)][1]*power(2,cnt[s^t^lowbit(s)])%mod)%=mod;
                if(!t) break;
            }
            f[s][1]=(power(2,cnt[s])-f[s][1]+mod)%mod;
        }
        for(register int j=2;j<=k;j++) {
            for(register int s=1;s<1<<n;s++) {
                for(register int t=(s-1)&s;t;t=(t-1)&s) {
                    (f[s][j]+=(int64)f[t][j-1]*f[s^t][1]%mod)%=mod;
                }
                f[s][j]=(int64)f[s][j]*inv(j)%mod;
            }
        }
        printf("Case #%d:\n%lld\n",i,(int64)f[(1<<n)-1][k]*power(2,tmp)%mod);
    }
    return 0;
}

转载于:https://www.cnblogs.com/skylee03/p/9657188.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值