Describe
有 n 个黑点与 m 个白点,其中第 i 个黑点与第 j 个白点之间有边的概率为 p i,j ,求期望最大匹配数。
对于 100% 的数据,n ≤ 5,m ≤ 1000。
DP
很显然是DP,而且应该与状压有关。 毕竟n这么小。
最初的想法是,要维护最大匹配,就对于最大匹配不能存在增广路。
这个想法有个缺陷:可能有多个匹配,如何将需要状态全部压起来?
其实很简单,不需要考虑增广路,直接将左侧所有可能匹配记为S (一个01状态)。即将多个最大匹配的左点状态存起来,例如是否存在一个将点{1,2}覆盖的匹配,是否存在将点{2,3}覆盖的匹配等。 (注意不一定最大的)
对应性:一种不同的连法只有一个对应状态。
这样每次枚举一个右侧点与其连边状态(2^5),再枚举旧状态S,先将S分解成若干个左点状态,然后考虑求S’.
首先显然S’包含S,那么对于某个左点状态,枚举每个不在其中的左点,假若这个点有边到当前右侧点,那么对应在S’中增加新状态。
答案也非常好计算,拿dp出来的f算一下sigma概率*最大匹配即可。
左点状态共有2^5个,那么S最多有2^(2^5)个?
注意到假如S中存在{1,2,3},那么{1,2},{1,3}…也一定存在。这似乎启示我们会有很多冗余状态?
按照转移方法写个暴搜,最终会发现当n=5时只有K=406个有效状态(数据来自题解)。
那么最终的时间:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <map>
#include <algorithm>
#define getloc(x,y) (((x)&(1<<(y)))!=0)
using namespace std;
typedef double db;
typedef long long ll;
int n,m,stm,ztot,o;
int has[11][1001],cnt[100];
db p[11][1001];
db ans;
db f[2][500];
map<ll,ll> H;
ll idex[20000],nex[1000][50];
void makestatu(ll s) {
if (H[s]!=0) return;
idex[++stm]=s; H[s]=stm;
int xxx=stm;
for (int con=0; con<ztot; con++) {
ll sp=s;
for (int i=0; i<ztot; i++) if (getloc(s,i)) {
for (int j=0; j<n; j++) if (getloc(i,j)==0 && getloc(con,j)==1) {
sp=sp|(1<<(i|(1<<j)));
}
}
makestatu(sp);
nex[xxx][con]=H[sp];
}
}
int main() {
freopen("shilingol.in","r",stdin);
// freopen("shilingol.out","w",stdout);
cin>>n>>m; ztot=1<<n;
for (int i=1; i<ztot; i++) cnt[i]=cnt[i/2]+(i&1);
for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) scanf("%lf",&p[i][j]);
makestatu(1);
f[o][H[1]]=1;
for (int i=1; i<=m; i++) {
o=1-o;
memset(f[o],0,sizeof f[o]);
for (int s=1; s<=stm; s++) if (f[1-o][s]>0) {
for (int con=0; con<ztot; con++) {
db gl=1;
for (int z=0; z<n; z++)
gl*=getloc(con,z)?p[z+1][i]:(1-p[z+1][i]);
f[o][nex[s][con]]+=f[1-o][s]*gl;
}
}
}
for (int s=1; s<=stm; s++) {
int mx=0; ll ss=idex[s];
for (int i=0; i<ztot; i++)
if (getloc(ss,i)) mx=max(mx,cnt[i]);
ans+=mx*f[o][s];
}
printf("%.6lf",ans);
}