题目链接

https://www.lydsy.com/JudgeOnline/problem.php?id=2891

题解

Hall定理:

设一个二分图的两边分别为U,V,对于点集SU,记录adj(S)为与S直接有边相连的点(邻集),显然adj(S)V

一个二分图U,V有完备匹配的充要条件是:对于任意一个SU|adj(S)||S|

令题目描述中点的数量为a的那边为UbV

定义Hall集合为满足上述条件的点集,所有的Hall集合的集合为HS集。显然,所有Hall集合都U

将所有HS集合对应到一个unsigned long long上面,第k位为1代表HS集合中有二进制状态为k的Hall集合。

由于Hall定理,HS集合的个数不会超过4000,因此可以用Hash/map映射到一个int上面。

fi,j代表V集合中已经选择了[1,i]这些点,U中HS集合在Hash/map中对应的int为j,造成这种情况的概率为fi,j

如果我们能够求出fi,j,那么最终的答案就是fb,HS×Max(HS),其中Max(HS)代表HS中最大的集合个数。

那么如何求fi,j?显然,f0,=1

考虑j状态能够转移得到的状态,令gj,k为:如果当前的HS为j,新加入的点的邻点为kkn个点的二进制状态),那么两个加起来的HS为gj,kj,gj,k为HS的二进制状态对应Hash/map中的int),那么显然有转移:fi,gj,k=fi1,j+P(i,k)(其中P(i,k)i点的邻点的二进制状态为k的概率)。

考虑如何求出gj,k。分情况讨论:

  • 如果k只有一个点,不妨设这个点的编号为x

    那么现在的集合中是HS集合的情况有两种:

    1. 原来就属于HS集合的集合。

    2. 加入了这个点x而变成了HS集合的集合。

      显然,这种集合满足|adj(S)|=|S|。经过理性分析可以得到:要满足上述条件,原HS集合必须包含S{x}这个集合。

    因此,gj,k=j+{S{x}j&(S{x})j}。其中,&表示条件之间的与(C++中的&&)。

  • 如果k有多个点。

    按上面的思路进行分析,显然可以得到:gj,k=j+{Sxk&{x}S&(S{x})j},其中&的意义同上。

那么我们就可以求出g数组了,由于求出的g数组中的元素都是合法的HS集合,因此可以边求g数组边将HS映射到Hash/map中。

时间复杂度:

  • g数组:
    • 第一维O(HS(n))
    • 第二维O(2n)
    • 转移O(n)
  • f数组:
    • 第一维O(m)
    • 第二维O(HS(n))
    • 转移O(2n)

总时间复杂度:O((n+m)×HS(n)×2n),其中HS(n)代表HS集合中每个元素大小不超过n的集合种类数。

代码

#include <cstdio>
#include <map>
#include <cmath>
#include <algorithm>

typedef unsigned long long ull;

const int maxn=6;
const int maxm=100;
const int maxd=4000;
const int maxk=(1<<maxn);
const double eps=0.001;

int n,m,g[maxd+10][maxk+3],full,cnt,size[maxn+10];
ull t[maxk+3],q[maxd+10];
std::map<ull,int> mp;
double p[maxn+2][maxm+5],f[maxm+5][maxd+10];

int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1; i<=n; ++i)
    {
      for(int j=1; j<=m; ++j)
        {
          scanf("%lf",&p[i][j]);
        }
    }
  q[1]=mp[1]=cnt=1;
  int l=0,r=1;
  while(l<r)
    {
      ull now=q[++l];
      for(int i=1; i<=n; ++i)
        {
          t[i]=now;
          for(int j=0; j<1<<n; ++j)
            {
              if((1ull<<j)&now)
                {
                  t[i]|=1ull<<(j|(1<<(i-1)));
                }
            }
        }
      for(int i=0; i<1<<n; ++i)
        {
          ull to=now;
          for(int j=1; j<=n; ++j)
            {
              if((1<<(j-1))&i)
                {
                  to|=t[j];
                }
            }
          if(!mp.count(to))
            {
              mp[to]=++cnt;
              q[++r]=to;
            }
          g[mp[now]][i]=mp[to];
        }
    }
  f[0][1]=1;
  for(int i=1; i<=m; ++i)
    {
      for(int j=1; j<=cnt; ++j)
        {
          if(f[i-1][j])
            {
              for(int k=0; k<1<<n; ++k)
                {
                  double e=1;
                  for(int h=1; h<=n; ++h)
                    {
                      if((1<<(h-1))&k)
                        {
                          e*=p[h][i];
                        }
                      else
                        {
                          e*=1-p[h][i];
                        }
                    }
                  f[i][g[j][k]]+=f[i-1][j]*e;
                }
            }
        }
    }
  double ans=0;
  for(int i=0; i<1<<n; ++i)
    {
      size[i]=size[i>>1]+(i&1);
    }
  for(int i=1; i<=cnt; ++i)
    {
      if(f[m][i])
        {
          int sz=0;
          for(int j=0; j<1<<n; ++j)
            {
              if((1ull<<j)&(q[i]))
                {
                  sz=std::max(sz,size[j]);
                }
            }
          ans+=f[m][i]*sz;
        }
    }
  printf("%.2lf\n",ans);
  return 0;
}