HDU5713 2016"百度之星" - 复赛(Astar Round3)K个联通块

一道状态压缩动态规划题

题目描述:众所周知,度度熊喜欢图,尤其是联通的图。今天,它在图上又玩出了新花样,新高度。有一张无重边的无向图, 求有多少个边集,使得删掉边集里的边后,图里恰好有 K 个连通块。 (k<=n<=14)

题解:状态压缩DP

一个简单的结论:删除边集的个数 = 选择边集的个数 , 这样我们就可以把问题转化为选取若干条边使得图里面恰好有K个联通块。

问题可以转化成三个状态压缩DP来完成(这三个DP都不难)

1、num[ mask ] 表示选取mask状态的点两两连边的边数。(这是一个很小的数)

这个数组我们通过状态压缩DP来求

令 u = lowbit (mask)  (即mask状态中编号最小的那个点)

枚举mask中的点v,计算边u,v的个数tmp

num [ mask ] = num [ mask ^u ] + tmp;


2、f [ mask ] 表示选取mask状态的点 ,构成一个联通块 的方案数

同样,利用上一次求出来的num数组,状态压缩DP来求

容斥原理 : 合法方案数 = 总方案数 - 不合法方案数

总方案数 :  2 ^ num( mask ) (每条边选或者不选)

不合法方案数 = tmp: 

令 u = lowbit (mask) 

枚举mask一个不包含u的子集s,tmp = tmp + f(mask ^ s) * ( 2 ^ num[ s ] );

上面的式子可以理解为枚举一个包含u的联通块,然后剩下的点两两之间的边选或者不选,这些边不会连接之前的联通块

f [ mask ] = 2 ^ num( mask) - tmp;


3、F[ mask ] [ n ]表示选取mask状态的点,构成n个联通块的方案数

实际上 f[ mask ]是 F[mask][1] ,剩下的状态压缩DP相对简单

枚举mask,枚举n

令u = lowbit( mask ),枚举mask一个不包含u的子集s

F[ mask ][ n ] = F[ s ][ n-1 ] * F[ mask ^ s ][ 1 ]

上面的式子等价于F[ mask ][ n ] = F [ s ][ n-1 ] * f[ mask ^ s ]


答案即为F[ mask ] [ n ] (mask为所有点都选择的状态,就是2^n - 1)

细节:

1、注意long long ,切记要写成 1LL << x,否则会TLE(亲身经历);

2、对1e9+7取模

3、注意枚举子集的方法


#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXMASK 65537
#define N 20
#define mod 1000000009LL

using namespace std;

int e[N][N],n,m,k,MASK,num[MAXMASK];
long long F[MAXMASK][N],f[MAXMASK];

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

int edge(int s, int p) {
    int ans = 0;
    for (int i = 1; i <= n; i++) 
        if ((1<<(i-1)) == p) {p = i;break;}
    for (int i = 1; i <= n; i++) if ((1<<(i-1))&s) ans += e[i][p];
    return ans;
}

int main()
{
    int T = 0;
    scanf("%d",&T);
    for (int rank=1;rank<=T;rank++)
    {
        memset(e,0,sizeof(e));
        memset(F,0,sizeof(F));
        memset(f,0,sizeof(f));
        memset(num,0,sizeof(num));
        scanf("%d%d%d",&n,&m,&k);
        MASK = (1 << n) - 1;
        for (int i=1;i<=m;i++) {
            int a,b;
            scanf("%d%d",&a,&b);
            e[a][b] = e[b][a] = 1;//顺带处理自环:) 
        }
        //计算mask状态下的边数 
        for (int i=1;i<=MASK;i++) {
            int t = lowbit(i);
            num[i] = num[i^t] + edge(i,t);//往状态里新增加一个点 
        }
        
        //状态压缩DP求小f 
        
        for (int s=1;s<=MASK;s++){
            int t = lowbit(s);
            long long tmp = 0LL;
            for (int i=(s^t);i>0;i=((i-1)&(s^t))) {
                tmp += f[s^i] * 1LL*(1LL << num[i]);
                tmp %= mod;
            }
            f[s] = (1LL*(1LL<<num[s]) - tmp) % mod;
        }
        
        //小f为大F的第一项 
        for (int s=1;s<=MASK;s++) F[s][1] = f[s];
        
        //状态压缩DP求大F 
        for (int s=1;s<=MASK;s++)
            for (int i=2;i<=k;i++) {
                int t = lowbit(s);
                for (int j=s^t;j>0;j=(j-1)&(s^t))  {//枚举s的子集
                    F[s][i] += F[j][i-1] * f[s^j];
                    F[s][i] %= mod;
                }
            }
        
        //防止答案小于0 
        while (F[MASK][k] < 0) F[MASK][k] += mod;
        printf("Case #%d:\n%I64d\n", rank, F[MASK][k]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值