[THUWC2017]随机二分图

时间:2022-02-12 14:59:46

https://www.zybuluo.com/ysner/note/1242342

题面

某人在玩一个非常神奇的游戏。这个游戏中有一个左右各\(n\)个点的二分图,图中的边会按照一定的规律随机出现。
为了描述这些规律,某人将这些边分到若干个组中。每条边只属于一个组。
有且仅有以下三类边的分组:(用\(t\)表示)

  • 这类组每组只有一条边,该条边恰好有\(50\%\)的概率出现。
  • 这类组每组恰好有两条边,这两条边有\(50\%\)的概率同时出现,有\(50\%\) 的概率同时不出现。
  • 这类组每组恰好有两条边,这两条边恰好出现一条,各有\(50\%\)的概率出现。

组和组之间边的出现都是完全独立的。
某人现在知道了边的分组和组的种类,询问完美匹配数量的期望是多少,输出\(2^nE\)\(E\)指期望)

  • \(20pts\ n\leq10\)
  • \(5pts\ t=0,m=n^2\)
  • \(15pts\ t=0\)
  • \(100pts\ n\leq15\)

    解析

    其实没有注意到某\(5pts\)的选手还是很。。。

可以把题目转化一下,应用映射思想,把左边点一一配对的右边点的顺序视作一个序列。(允许不配对
如左\(1\)配右\(2\),左\(2\)配右\(4\),左\(3\)不配,左\(4\)配右\(1\)
形成序列为\(24\_1\)

\(5pts\ m=n^2\)

很显然这个(映射)序列可以是全排列。
边的总方案数\(2^m\),完美匹配方案数\(n!2^{m-n}\),则\[E=\frac{n!2^{m-n}}{2^m}=\frac{n!}{2^n}\]
最后\(ans=n!\)

\(20pts\)算法

我们可以枚举一下点完美匹配的方案,再统计方案数。

但是怎么反映边与边之间的关系呢?
这一点可以联想一下网络流的技巧:拆点。
第一类不用说。
第二类两条边的编号相同。
第三类两条边的编号差\(m\)
依此,在检验合法性时,若\(i\)\(i+m\)同时存在即不合法;
在统计方案数时,在该匹配下\(i\)\(i+m\)均未用到,则该组边出不出现皆可,该匹配下方案数\(*2\)

最后,边出现的总方案数为\(2^m\),完美匹配方案数为\(ans\),则期望为\(E=\frac{ans}{2^m}\)
最终答案为\[\frac{ans*2^n}{2^m}\]
复杂度\(O(n!m)\)

il void check()
{
    memset(b,0,sizeof(b));
    fp(i,1,n) b[p[i]]=1;
    re ll sum=1;
    fp(i,1,m)
    {
        if(b[i]&&b[i+m]) return;
        if(!b[i]&&!b[i+m]) (sum*=2)%=mod;
    }
    (ans+=sum)%=mod;
}
il void dfs(re int x)
{
    if(x>n) {check();return;}
    fp(i,1,n)
        if(!vis[i]&&id[x][i])
        {
            p[x]=id[x][i];vis[i]=1;
            dfs(x+1);
            vis[i]=0;
        }
}
int main()
{
  n=gi();m=gi();
    if(m==n*n)
    {
        ans=1;
        fp(i,1,n) (ans*=i)%=mod;
        printf("%lld\n",ans);
      return 0;
    }
    if(n<=10)
    {
    fp(i,1,m)
    {
        re int t=gi(),u=gi(),v=gi(),u1,v1;
        if(t) u1=gi(),v1=gi();
        if(t==0) id[u][v]=i;
        if(t==1) id[u][v]=id[u1][v1]=i;
        if(t==2) id[u][v]=i,id[u1][v1]=i+m;
    }
    dfs(1);
    printf("%lld\n",ans*ksm(ksm(2,m),mod-2)%mod*ksm(2,n)%mod);
    }
  return 0;
}

\(40pts\)算法

其实我不太会???

\(100pts\)算法

把双边组看做互不干扰的、出现概率为\(50\%\)的边。如果这样,第二类组合边出现概率会少算
\(25\%\)(同时出现的概率为\(25\%\)),第三类组合边出现概率会多算\(25\%\)(只出现一条的概率为\(50\%\))。我们可以分别为这两组建\(+25\%,-25\%\)的边将这个概率抵消(因为各组独立)。

举个例子,在第二类中,两条边同时出现的概率为\(50\%*50\%+25\%=50\%\),不同时出现的概率为\(1-25\%-(50\%*50\%)=50\%\),符合要求。

然后记忆化搜索,\(f[S]\)表示\(S\)集合内的点的完美匹配的期望方案数,为了保证选边的有序性并同时减少状态数,\(f[S]\)\(f[S1]\)转移过来时,要求\(S\)最高位比\(S1\)的最高位高

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#define re register
#define il inline
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int mod=1e9+7,N=10000,inv2=mod+1>>1,inv4=mod+1>>2;
struct node
{
  int s,w;
  il node(){s=w=0;}
  il node(re int x,re int y){s=x,w=y;}
}a[N];
int tot,n,m;
map<int,int>f[1<<16];
il int gi()
{
   re int x=0,t=1;
   re char ch=getchar();
   while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
   if(ch=='-') t=-1,ch=getchar();
   while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
   return x*t;
}
il int dfs(re int S)
{
  if(!S) return 1;
  re int T0=S>>n,S0=S^(T0<<n);
  if(f[S0].count(T0)) return f[S0][T0];
  re int &tmp=f[S0][T0];
  fp(i,1,tot)
    {
      re int T=a[i].s;
      if((T&S)==T&&S<(T<<1)) (tmp+=1ll*dfs(S^T)*a[i].w%mod)%=mod;
    }
  return tmp;
}
int main()
{
  n=gi();m=gi();
  fp(i,1,m)
    {
      re int t=gi(),u=gi(),v=gi(),u1,v1;
      re int S1=(1<<(u-1))|(1<<(v+n-1));
      a[++tot]=node(S1,inv2);
      if(t)
    {
      u1=gi(),v1=gi();
      re int S2=(1<<u1-1)|(1<<v1+n-1);
      a[++tot]=node(S2,inv2);
          if(S1&S2) continue;
          if(t==1) a[++tot]=node(S1|S2,inv4);
          if(t==2) a[++tot]=node(S1|S2,mod-inv4);
    }
    }
  //printf("%d\n",tot);
  printf("%lld\n",(1ll<<n)*dfs((1ll<<(2*n))-1)%mod);
  return 0;
}