题目大意:给出n个点和m条边,求出图中恰好有k个连通图的方案数。
首先我们求出每个选择状态的连边方案数num,为了使枚举时不重复,
每次去掉最后一位lowbit( i ),只用包含这个点的连边情况转移。
然后求出保证这个状态只有1个联通块的方案数f,枚举所有状态
利用去掉最后一位lowbit( i )之后的状态s,枚举s的子集j,
i^j就代表了原状态i中不含j状态的情况,剩余的j状态的连边共有2的num[j]次幂种
利用f[i^j]这个原状态内部只有一个联通块的方案数 * 剩余的j状态的连边方案总数
就是在i状态中利用f[i^j]求出的不满足的方案数(因为这样就大于1个联通块了)。
dp数组代表i状态有j个联通块的情况数。
最后枚举状态,和状态的子集,即dp[i][q] = dp[ j ][q-1] * f[i - j ] (也可以写成i^j)
最后输出满状态的情况。
下面代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define mode 1000000009
using namespace std;
typedef long long ll;
ll num[(1<<16)+5];
ll f[(1<<16)+5];
ll dp[(1<<16)+5][16];
ll v[205];
int dis[16][16];
int lowbit(int x)
{
return x & -x;
}
void init()
{
memset(num,0,sizeof(num));
memset(f,0,sizeof(f));
memset(dp,0,sizeof(dp));
memset(dis,0,sizeof(dis));
}
int n,m,k;
int gett(int x)
{
int flag = 0;
int cnt = 0;
for(int i = 1;i <= n;i++)
{
if((1<<(i-1))&x)
{
flag = i;
break;
}
}
for(int i = 1;i <= n;i++)
{
if((1<<(i-1))&x)
{
if(dis[flag][i])cnt++;
}
}
return cnt;
}
int main()
{
int T;
v[0] = 1;
scanf("%d", &T);
for(int i = 1;i <= 200;i++)
{
v[i] = v[i-1]*2%mode;
}
for(int w = 1;w <= T;w++)
{
scanf("%d%d%d", &n, &m, &k);
init();
for(int i = 1;i <= m;i++)
{
int a,b;
scanf("%d%d", &a, &b);
dis[a][b] = dis[b][a] = 1;
}
for(int i = 1;i <= (1<<n)-1;i++)
{
num[i] = num[i^lowbit(i)]+gett(i);
}
for(int i = 1;i <= n;i++)
{
f[(1<<(i-1))] = 1;
}
for(int i = 1;i <= (1<<n)-1;i++)
{
int s = i^lowbit(i);
ll tt = 0;
for(int j = s;j;(--j)&= s)
{
tt += f[j^i]*v[num[j]]%mode;
tt %= mode;
}
f[i] = ((v[num[i]]-tt)%mode+mode)%mode;
}
if(k==1)
{
printf("Case #%d:\n%lld\n",w,f[(1<<n)-1]);
continue;
}
for(int i = 0;i <= (1<<n)-1;i++)
{
dp[i][1] = f[i];
}
for(int i = 1;i <= (1<<n)-1;i++)
{
int s = i^lowbit(i);
for(int j = s;j;(--j)&= s)
{
for(int q = 2;q <= k;q++)
{
dp[i][q] += dp[j][q-1]*f[i^j]%mode;
dp[i][q] %= mode;
}
}
}
printf("Case #%d:\n%lld\n",w,dp[(1<<n)-1][k]);
}
return 0;
}