把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【LOJ2290】「THUWC2017」随机二分图(状压+记忆化搜索)

点此看题面

大致题意: 一张二分图,有三类边:第一组有一条边,\(\frac12\)的概率出现;第二组有两条边,\(\frac12\)的概率同时出现,否则都不出现;第三组有两条边,只能出现恰好一条,各有\(\frac12\)的概率出现。求二分图完美匹配数量的期望。

状压

考虑计算每一种完美匹配出现的概率,然后将这概率求和,即为答案。

首先考虑只有\(t=0\)的情况。

对于每条边,我们用\(s_i\)表示它的两个端点状压后的结果,\(p_i\)表示这条边出现的概率(当\(t=0\)时,皆为\(\frac12\))。

然后我们可以考虑一个子集\(DP\),用\(f_x\)表示子集\(x\)内完美匹配的个数期望,那么可以每次枚举一条端点都在点集中的边\(i\),则可以得到转移:

\[f_x=\sum(p_i\cdot f_{x\ xor\ s_i}) \]

注意到此时可能会由于一组完美匹配每条边的出现顺序不同而导致答案的重复计算,所以我们强制点集\(x\)中编号最小的点一定要被选,即满足\(lowbit(x)\&s_i≠0\)

题目中给出\(n\)的范围是\(n\le15\),但由于这是个二分图,所以真正的点数是\(2n\)。也就是说\(f_x\)\(x\)的取值范围是\(0\le x<2^{30}\),似乎开不下这么大的数组。

因此我们不得不把子集\(DP\)改为记忆化搜索。

扩展

上面我们只考虑了只有\(t=0\)时的情况,接下来便要考虑扩展。

考虑对于\(t=1\)\(t=2\)时的一组边,我们先把这两条边都加入边集之中,设它们分别为\(i\)\(j\)

首先容易发现,如果只选择两条边中的一条边,那么概率\(p_i\)\(p_j\)依旧是正常的\(\frac12\)

这么一来,我们就可以推理出,若\(s_i\&s_j≠0\),即两条边有交点时,由于是要匹配,则它们必然不可能被同时选择,因此它们的概率必然是正常的。

但是,如果\(s_i\&s_j=0\),这时候我们就要考虑两条边同时出现了。

正常情况下,\(i\)\(j\)同时被选择的概率显然是\((\frac12)^2=\frac14\),但当\(t=1\)\(t=2\)时,\(i\)\(j\)同时被选择的概率分别就应该是\(\frac12\)\(0\),与原来的概率相比,分别是增加了\(\frac14\)\(-\frac14\)

实际上,我们可以加入一个边组\(s_k=s_i|s_j\)\(p_k=\frac14\)\(-\frac14\)

由于选择了\(i\)\(j\)中的任意一条边,就不可能再选择边组\(k\),反之亦然。因此,我们可以把两条边和边组独立看待。

然后我们就可以发现,经过加入的这个边组的调整,此时的概率就符合了题目要求。

这样一来,我们就做完了这道题。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 15
#define M N*N
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,cnt,s[3*M+5],p[3*M+5];map<int,int> f;
I int dfs(CI x)//记忆化搜索
{
	if(!x) return 1;if(f.count(x)) return f[x];RI t=0;//对于空集,返回1;若已搜索过,直接返回答案
	for(RI i=1;i<=cnt;++i) (x&-x)&s[i]&&(x&s[i])==s[i]&&(t=(1LL*p[i]*dfs(x^s[i])+t)%X);//枚举边,继续搜索
	return f[x]=t;//记忆化
}
int main()
{
	RI i,op,x,y,I2=X+1>>1,I4=X+1>>2;for(scanf("%d%d",&n,&m),i=1;i<=m;++i)
	{
		scanf("%d%d%d",&op,&x,&y),s[++cnt]=(1<<x-1)|(1<<n+y-1),p[cnt]=I2;if(!op) continue;//读入第一条边
		scanf("%d%d",&x,&y),s[++cnt]=(1<<x-1)|(1<<n+y-1),p[cnt]=I2;if(s[cnt-1]&s[cnt]) continue;//读入第二条边
		++cnt,s[cnt]=s[cnt-2]|s[cnt-1],p[cnt]=(op==1?I4:X-I4);//加入边组
	}return printf("%d",(1LL<<n)*dfs((1<<(2*n))-1)%X),0;//输出答案,注意题目中要求答案乘上2^n
}
posted @ 2019-12-23 17:32  TheLostWeak  阅读(176)  评论(0编辑  收藏  举报