THUWC2017 随机二分图
随机二分图
某人在玩一个非常神奇的游戏。这个游戏中有一个左右各 \(n\) 个点的二分图,图中的边会按照一定的规律随机出现。
为了描述这些规律,某人将这些边分到若干个组中。每条边或者不属于任何组 (这样的边一定不会出现),或者只属于一个组。
有且仅有以下三类边的分组:
-
这类组每组只有一条边,该条边恰好有 \(50\%\) 的概率出现。
-
这类组每组恰好有两条边,这两条边有 \(50\%\) 的概率同时出现,有 \(50\%\) 的概率同时不出现。
-
这类组每组恰好有两条边,这两条边恰好出现一条,各有 \(50\%\) 的概率出现。
组和组之间边的出现都是完全独立的。
某人现在知道了边的分组和组的种类,想要知道完美匹配数量的期望是多少。你能帮助她解决这个问题吗?
定义解释
如果你对完美匹配和期望的定义很熟悉,那么你可以跳过本段。
对于一个左右各 \(n\) 个点的二分图,它的一个完美匹配是指 \(n\) 条没有公共点的边构成的匹配。
两个完美匹配不同,当且仅当它们至少含有一条不同的边。一个二分图完美匹配的数量定义为这张图能找到的两两不同的完美匹配的数量。
在题目的图中,边都是随机出现的,因此这个图中完美匹配的数量是一个随机变量。一个(离散型)随机变量 \(X\) 的期望定义为以概率为权,\(X\) 所有可能取值的加权平均数,即
其中 \(V(X)\) 表示 \(X\) 所有可能的取值集合,\(P[X=x]\) 表示 \(X\) 取值为 \(x\) 的概率。
对于 \(100\%\) 的数据,\(n \le 15\)。
mangoyang的题解
首先有一个 40pts 的做法:
前 20pts 暴力枚举最终的匹配是怎样的,check一下计算方案数,后 20pts 令 \(f[s][i]\) 表示当前左边的点匹配到前 \(i\) 个,右边的点匹配状况是 \(s\) 时继续往下匹配方案数的期望,枚举与 \(i\) 相连的边转移即可。
对于剩下的 t=1,t=2 的情况,先和 t = 0 一样直接连 \((a_1,b_1), (a_2,b_2)\)。然后观察此时概率发生的偏差。
以 t=1 为例,只选 \((a_1,b_1)\) 或者只选 \((a_2, b_2)\) 时概率和正确情况一样都是 \(\frac{1}{2}\) 。但是如果两条边都选此时算的概率是 \(\frac{1}{4}\) ,而实际上应该是 \(\frac{1}{2}\) ,所以还要补连一种转移同时选上四个点概率是 \(\frac{1}{4}\) ,根据期望的线性性,正确性显然。
对于 t=2 情况,和上面一样分析,发现对于同时选的情况多算了 \(\frac{1}{4}\) ,补连一条概率是 \(-\frac{1}{4}\) 的转移即可。
此时我们就不能按照 40pts 的方法DP了,需要设 \(f[s_1][s_2]\) 表示此时左边点匹配状况是 \(s_1\),右边匹配状况是 \(s_2\) ,继续向下匹配方案数的期望。但是为了不重,我们每次还是要为 \(s_1\) 中编号最小为匹配的点安排匹配,那么这样状态数就是和 40pts 的转移同阶的,用一个map记忆化一下复杂度就是 \(O(n^22^n)\)。
复杂度计算
虽然我们状态 \(f[s_1][s_2]\) 有 \(2^{2n}\) 中,但是不是每个状态都走得到。显然只有二分图左右两侧点数相等的点才走得到。又因为我们每次都是转移左侧最小标号的点,所以
有 t=1,2 时状态还要多一些,不过影响不大。
int n,m;
int a[600],b[600],cnt; // edit 1: 300 -> 600
unordered_map<int,int> f;
int dfs(int mask){
if(mask==(1<<2*n)-1) return 1;
if(f.count(mask)) return f[mask];
int now=0;
for(int i=n-1;i>=0;--i)
if(~mask>>i&1) now=1<<i;
int ans=0;
for(int i=1;i<=cnt;++i)if(now&a[i] and !(mask&a[i]))
ans=add(ans,1LL*dfs(mask|a[i])%mod*b[i]%mod);
return f[mask]=ans;
}
int main(){
// freopen("LOJ2290.in","r",stdin);
read(n),read(m);
while(m--){
int opt=read<int>();
if(opt==0){
int x=read<int>()-1,y=read<int>()-1;
a[++cnt]=1<<x|1<<(y+n),b[cnt]=i2;
}
else if(opt==1){
int x=read<int>()-1,y=read<int>()-1;
a[++cnt]=1<<x|1<<(y+n),b[cnt]=i2;
x=read<int>()-1,y=read<int>()-1;
a[++cnt]=1<<x|1<<(y+n),b[cnt]=i2;
if(a[cnt-1]&a[cnt]) continue;
++cnt,a[cnt]=a[cnt-2]|a[cnt-1],b[cnt]=i4;
}
else{
int x=read<int>()-1,y=read<int>()-1;
a[++cnt]=1<<x|1<<(y+n),b[cnt]=i2;
x=read<int>()-1,y=read<int>()-1;
a[++cnt]=1<<x|1<<(y+n),b[cnt]=i2;
if(a[cnt-1]&a[cnt]) continue;
++cnt,a[cnt]=a[cnt-2]|a[cnt-1],b[cnt]=mod-i4;
}
}
printf("%lld\n",1LL*dfs(0)*fpow(2,n)%mod);
return 0;
}