题目链接
https://www.lydsy.com/JudgeOnline/problem.php?id=2891
题解
Hall定理:
设一个二分图的两边分别为,对于点集,记录为与直接有边相连的点(邻集),显然。
一个二分图有完备匹配的充要条件是:对于任意一个,。
令题目描述中点的数量为的那边为,为。
定义Hall集合为满足上述条件的点集,所有的Hall集合的集合为HS集。显然,所有Hall集合都。
将所有HS集合对应到一个unsigned long long上面,第位为代表HS集合中有二进制状态为的Hall集合。
由于Hall定理,HS集合的个数不会超过,因此可以用Hash/map映射到一个int上面。
设代表集合中已经选择了这些点,中HS集合在Hash/map中对应的int为,造成这种情况的概率为。
如果我们能够求出,那么最终的答案就是,其中代表中最大的集合个数。
那么如何求?显然,。
考虑状态能够转移得到的状态,令为:如果当前的HS为,新加入的点的邻点为(为个点的二进制状态),那么两个加起来的HS为(为HS的二进制状态对应Hash/map中的int),那么显然有转移:(其中为点的邻点的二进制状态为的概率)。
考虑如何求出。分情况讨论:
如果只有一个点,不妨设这个点的编号为。
那么现在的集合中是HS集合的情况有两种:
原来就属于HS集合的集合。
加入了这个点而变成了HS集合的集合。
显然,这种集合满足。经过理性分析可以得到:要满足上述条件,原HS集合必须包含这个集合。
因此,。其中,表示条件之间的与(C++中的)。
如果有多个点。
按上面的思路进行分析,显然可以得到:,其中的意义同上。
那么我们就可以求出数组了,由于求出的数组中的元素都是合法的HS集合,因此可以边求数组边将HS映射到Hash/map中。
时间复杂度:
- 求数组:
- 第一维
- 第二维
- 转移
- 求数组:
- 第一维
- 第二维
- 转移
总时间复杂度:,其中代表HS集合中每个元素大小不超过的集合种类数。
代码
#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;
}