[容斥 状压DP] HDU4997. Biconnected

22 篇文章 1 订阅
10 篇文章 0 订阅

fS 表示点集 S 的答案,gS 表示点集 S 的连通图个数

那么 gS 可以通过枚举与编号最小的点联通的点集求出来

fS=gSTSgT×MT,ST MS,T 表示把点集 S 分成几个联通块后连到 T 上的方案数

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int P=1e9+7,N=1<<12;

int t,n,m;
int e[15][15],f[N],size[N],edge[N],g[N],ug[N],h[N],mer[1<<10|5][1<<10|5],pw[1010];

int G(int S){
  if(~g[S]) return g[S];
  int ret=pw[edge[S]],lst=S&-S; S^=lst;
  for(int s=S;s;s=(s-1)&S)
    ret=(ret-1LL*G(S^s^lst)*pw[edge[s]])%P;
  return g[S^lst]=ret;
}

int Edge(int S,int T){
  return edge[S^T]-edge[S]-edge[T];
}

int M(int S,int T){
  if(!Edge(S,T)) return 0;
  if(~mer[S][T]) return mer[S][T];
  int ret=1LL*G(S)*Edge(S,T)%P,lst=S&-S; S^=lst;
  for(int s=S;s;s=(s-1)&S){
    ret=(ret+1LL*M(s,T)*G(S^s^lst)%P*Edge(S^s^lst,T))%P;
  }
  return mer[S^lst][T]=ret;
}

int F(int S){
  if(~f[S]) return f[S];
  int ret=G(S),lst=S&-S; S^=lst;
  for(int s=S;s;s=(s-1)&S)
    ret=(ret-1LL*F(S^s^lst)*M(s,S^s^lst))%P;
  return f[S^lst]=ret;
}

int main(){
  freopen("1.in","r",stdin);
  freopen("1.out","w",stdout);
  scanf("%d",&t);
  for(int i=1;i<(1<<10);i++) size[i]=size[i>>1]+(i&1);
  pw[0]=1; for(int i=1;i<=100;i++) pw[i]=2LL*pw[i-1]%P;
  while(t--){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++) e[i][j]=1;
    for(int i=1,x,y;i<=m;i++)
      scanf("%d%d",&x,&y),e[x][y]=e[y][x]=0;
    memset(mer,-1,sizeof(mer));
    for(int i=0;i<(1<<n);i++)
      f[i]=g[i]=ug[i]=h[i]=-1;
    for(int S=1;S<(1<<n);S++){
      if(size[S]==1) continue;
      edge[S]=0;
      for(int i=1;i<=n;i++)
    for(int j=i+1;j<=n;j++)
      if(i!=j && (S>>(i-1)&1) && (S>>(j-1)&1))
        edge[S]+=e[i][j];
    }
    int cur=F(5);
    printf("%d\n",(P+F((1<<n)-1))%P);
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值