zoukankan      html  css  js  c++  java
  • 【LOJ2290】「THUWC2017」随机二分图(状压+记忆化搜索)

    点此看题面

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

    状压

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

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

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

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

    [f_x=sum(p_icdot f_{x xor s_i}) ]

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

    题目中给出(n)的范围是(nle15),但由于这是个二分图,所以真正的点数是(2n)。也就是说(f_x)(x)的取值范围是(0le 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
    }
    
  • 相关阅读:
    使用纯 CSS 实现响应式的图片显示效果
    10个帮助你快速调试和排错的小技巧
    《JavaScript 实战》:JavaScript 实现拖拽缩放效果
    周末发福利了!26个免费的HTML5模版
    程序人生的四个象限和两条主线
    50份简历设计,助你找到梦寐以求的工作
    6个重构方法可帮你提升 80% 的代码质量
    开发者必须收藏的6款源码搜索引擎
    常用的20个强大的 Sublime Text 插件
    你知道吗?.NET Framework 4.5 五个很棒的特性
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/LOJ2290.html
Copyright © 2011-2022 走看看