题目
题解
从这里开始的一系列HDU题目都是集训期间的呢qwq,真的很棒。
我觉得我要能现场做出来这道题的话,起码得省选之后了。
一个比较显然的状态及方程是,dp[k][S]表示S中的点构成k个联通块的方法数(貌似删边和选一个边集是一一对应的)。
dp[k][S]=∑S′⊂Sdp[k−1][S−S′]∗dp[1][S′]
为了避免重复,可以限制必须取最低位的那个元素x,(我的方法是先把x抠出来,然后枚举剩下的子集,再把x与回去)
dp[1][S]如何计算?
用cnt[S]表示S集合内部的边条数,g[S]表示S集合内部不为1个连通块的方案数,有
g[S]=∑S′⊂Sdp[1][S′]∗2cnt[S−S′]
dp[1][S]=2cnt[S]−g[S]
计算完毕。
代码
//QWsin
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=20;
const int MOD=1e9+7;
const int maxS=(1<<14)+10;
typedef long long ll;
ll dp[maxn][maxS],g[maxS],c2[250];
int n,m,k,cnt[maxS],G[maxn][maxn],kase=0;
#define lowbit(x) ((x)&-(x))
inline void solve()
{
memset(dp,0,sizeof dp);
memset(G,0,sizeof G);
scanf("%d%d%d",&n,&m,&k);
for(int i=1,u,v;i<=m;++i){
scanf("%d%d",&u,&v);++G[u][v];
if(v!=u)++G[v][u];
}
int ALL=(1<<n)-1;
//cnt[S]表示集合内边的条数
for(int S=1,pos=0;S<=ALL;++S,pos=0)
{
while(!(S & 1<<pos)) ++pos;
cnt[S]=cnt[S^(1<<pos)];
for(int j=pos;j<n;++j) if(S & 1<<j)
cnt[S]+=G[pos+1][j+1];
}
//g[S]表示集合内点不连通的方案数
for(int S=1;S<=ALL;++S)
{
int t=lowbit(S);g[S]=0;
for(int S0=((S^t)-1)&(S^t);;S0=(S0-1)&(S^t))
{
g[S]+=dp[1][S0^t]*c2[cnt[S^S0^t]];
if(!S0) break;
}
dp[1][S]=(c2[cnt[S]]-g[S])%MOD;
if(dp[1][S] < 0) dp[1][S]+=MOD;
}
//dp[k][S]表示S里的点组成k个连通块的方案数
for(int i=2;i<=k;++i)
for(int S=1;S<=ALL;++S)
{
int t=lowbit(S);
for(int S0=((S^t)-1)&(S^t);;S0=(S0-1)&(S^t))
{
dp[i][S]+=dp[i-1][S^S0^t]*dp[1][S0^t];
if(dp[i][S]>=MOD) dp[i][S]%=MOD;
if(!S0) break;
}
}
printf("Case #%d:\n%lld\n",++kase,dp[k][ALL]);
}
int main()
{
c2[0]=1;for(int i=1;i<=249;++i)c2[i]=(c2[i-1]<<1)%MOD;
int T;cin>>T;
while(T--) solve();
return 0;
}