HDU 5713 K个联通块(状压DP)

40 篇文章 1 订阅

Description

众所周知,度度熊喜欢图,尤其是联通的图。

今天,它在图上又玩出了新花样,新高度。有一张无重边的无向图, 求有多少个边集,使得删掉边集里的边后,图里恰好有K个连通块。

Input

第一行为T,表示输入数据组数。

对于每组数据,第一行三个整数N,M,K,表示N个点M条边的图。
接下来M行每行两个整数a,b,表示点a和点b之间有一条边。

1≤T≤20

1≤K≤N≤14

0≤M≤N∗(N+1)/2
1≤a,b≤N

Output

对第i组数据,输出

Case #i:

然后输出一行,仅包含一个整数,表示方法种数(对 1 000 000 009 取模) 。

Sample Input

3
1 0 1
1 1 1
1 1
3 3 2
1 2
2 3
1 3

Sample Output

Case #1:
1
Case #2:
2
Case #3:
3

Solution

状压,拿一个状态 i 表示一个点集,i的二进制第 j 位为1说明选第j个点,否则不选

dp[i][k] 表示点集 i 构成k个连通块的方案数

f[i] 表示删掉 i 点集中若干边该点集还是一个连通块的方案数

枚举i点集中编号最小的点所处连通块即得转移方程:

dp[i][k]=jdp[ij][k1]f[j] j 为所有包含i状态中编号最小点的 i 的子状态

对于f[i],直接求比较困难,考虑求其反

g[i] 为删掉 i 点集中若干边该点集超过一个连通块的方案数

cnt[i] i 点集中边的数量,则f[i]+g[i]=2cnt[i]

类似的,考虑 i 点集中编号最小的点所处连通块,对该连通块中的边需要满足删掉后还是一个连通块,该连通块与其它点之间的边都删掉,剩下的边随便删,故有转移方程:

g[i]=jf[j]2cnt[ij], j 为所有包含 i 状态中编号最小点的 i 的子状态

答案即为 dp[2N1][K]

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define maxn (1<<15)
#define mod 1000000009
int Case=1,T,n,m,K,a[15][15];
ll dp[maxn][15],cnt[maxn],f[maxn],g[maxn],b[111]; 
int Solve(int x)
{
    int ans=0;
    for(int i=0;i<n;i++)
        if(x&(1<<i))
            for(int j=i;j<n;j++)
                if(x&(1<<j)&&a[i][j])ans++;
    return ans;
}
int main()
{
    b[0]=1;
    for(int i=1;i<=105;i++)b[i]=b[i-1]*2%mod;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&m,&K);
        memset(a,0,sizeof(a));
        while(m--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            u--,v--;
            a[u][v]++;
            if(u!=v)a[v][u]++;
        }
        int N=b[n];
        for(int i=0;i<N;i++)cnt[i]=Solve(i);
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        memset(dp,0,sizeof(dp));
        for(int i=1;i<N;i++)
        {
            int j=i;
            while(j)
            {
                if(j&(i&-i))g[i]=(g[i]+b[cnt[i-j]]*f[j])%mod;
                j=(j-1)&i;
            }
            f[i]=(b[cnt[i]]-g[i]+mod)%mod;
        }
        dp[0][0]=1;
        for(int i=1;i<N;i++)
            for(int k=1;k<=K;k++)
            {
                int j=i;
                while(j)
                {   
                    if(j&(i&-i))dp[i][k]=(dp[i][k]+dp[i-j][k-1]*f[j])%mod;
                    j=(j-1)&i;
                }
            }
        printf("Case #%d:\n%lld\n",Case++,dp[N-1][K]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值