zoukankan      html  css  js  c++  java
  • 随机二分图

    题目描述

    某人在玩一个非常神奇的游戏。这个游戏中有一个左右各 (n) 个点的二分图,图中的边会按照一定的规律随机出现。

    为了描述这些规律,某人将这些边分到若干个组中。每条边只属于一个组。

    有且仅有以下三类边的分组:

    • 1.这类组每组只有一条边,该条边恰好有 (50\%) 的概率出现。

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

    • 3.这类组每组恰好有两条边,这两条边恰好出现一条,各有 (50\%) 的概率出现。

    组和组之间边的出现都是完全独立的。

    某人现在知道了边的分组和组的种类,想要知道完美匹配数量的期望是多少。你能帮助她解决这个问题吗?

    输入格式

    从标准输入读入数据。

    第一行两个数 (n)(m),表示图左右点数的数量和边的组的个数。我们用 ((a,b)) (其中 (1le a,ble n))表示一条左端点为二分图左侧第 (a) 个点,右端点为二分图右侧第 (b) 个点的边。

    接下来 (m) 行,每行描述一个组。开头第一个数 (t) 表示组的种类,(0) 表示是一条边的组,(2) 表示是两条边的组中的第一种,(2) 表示是两条边的组中的第二种。如果 (t=0), 接下来两个数 (a,b) 表示组内的第一条边;否则,接下来四个数 (a1,ba,a2,b2), 表示该组内的两条边分别为 ((a1,b1))((a2,b2))

    保证每条边至多出现一次。

    输出格式

    输出到标准输出。

    假设期望的完美匹配数量是 (E)。输出一行表示

    [(2^nE) mod (1e9+7) ]

    可以看出上式一定是一个整数。

    其实这题不是很难吧(只要你抱着骗分的心态去做)

    看到 (nle 15) 肯定能想到状压,但是状压 (2^{2n}) 复杂度不对啊? 那就别循环了,直接记搜

    先考虑 (t=0) 的情况

    由于每个点一定需要匹配一条边,直接扫每个左边的点,同时枚举他的出边选那一条就行了

    然后考虑 (t=1/2)

    (t=1) 时,如果我们像 (t=0) 一样枚举出边且每次概率乘以 (frac{1}{2}),发现当同时选择 ((a1,b1))((a2,b2)) 时贡献只有 (frac{1}{4})

    同理当 (t=2) 时,当同时选择 ((a1,b1))((a2,b2)) 时贡献多了 (frac{1}{4})

    考虑怎么补回来这些损失和花费

    如果我们有一种情况中同时选择了 ((a1,b1))((a2,b2)) ,那么如果建出一条 ((a1,b1),(a2,b2)) 的边,一定会在某种情况被选上

    那么对于 (t=1),我们建出一条 ((a1,b1)·(a2,b2)) 的边,使它的概率为 (frac{1}{4}),这样可以补回来少的 (frac{1}{4})

    对于 (t=2),我们建出一条 ((a1,b1)·(a2,b2)) 的边,使它的概率为 (-frac{1}{4}),这样可以加上多加的 (frac{1}{4})

    然后记搜就行了

    复杂度什么的不重要就当$(能过)

    code
    
    #include<bits/stdc++.h>
    #define ll long long
    #define cri const register int
    #define re register
    using namespace std;
    const int mod=1e9+7,inv2=500000004,inv4=250000002;
    vector<int>is[16],e[16];
    unordered_map<int,int>mp;
    int s[40000];
    int dfs(cri now){
    	if(!now) return 1;
    	if(mp.find(now)!=mp.end()) return mp[now];
    	int p=s[now&-now],ans=0;
    	for(int i=0;i<is[p].size();i++)
    		if((now&is[p][i])==is[p][i])
    			ans=(ans+1ll*dfs(now^is[p][i])*e[p][i]%mod)%mod;
    	return mp[now]=ans;
    }
    int main(){
    	int n,m;
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++) s[1<<i-1]=i;
    	while(m--){
    		int opt,a1,a2,b1,b2;
    		scanf("%d",&opt);
    		if(!opt){
    			scanf("%d%d",&a1,&b1);
    			int tmp=(1<<a1-1)|(1<<b1+n-1);
    			is[a1].push_back(tmp);
    			e[a1].push_back(inv2);
    		}
    		if(opt==1){
    			scanf("%d%d%d%d",&a1,&b1,&a2,&b2);
    			int tmp1=(1<<a1-1)|(1<<b1+n-1),tmp2=(1<<a2-1)|(1<<b2+n-1),tmp=tmp1|tmp2;
    			is[a1].push_back(tmp1);e[a1].push_back(inv2);
    			is[a2].push_back(tmp2);e[a2].push_back(inv2);
    			if(a1!=a2&&b1!=b2){
    				is[a1].push_back(tmp);e[a1].push_back(inv4);
    				is[a2].push_back(tmp);e[a2].push_back(inv4);
    			}
    		}
    		if(opt==2){
    			scanf("%d%d%d%d",&a1,&b1,&a2,&b2);
    			int tmp1=(1<<a1-1)|(1<<b1+n-1),tmp2=(1<<a2-1)|(1<<b2+n-1),tmp=tmp1|tmp2;
    			is[a1].push_back(tmp1);e[a1].push_back(inv2);
    			is[a2].push_back(tmp2);e[a2].push_back(inv2);
    			if(a1!=a2&&b1!=b2){
    				is[a1].push_back(tmp);e[a1].push_back(mod-inv4);
    				is[a2].push_back(tmp);e[a2].push_back(mod-inv4);
    			}
    		}
    	}
    	int ans=dfs((1<<n+n)-1);
    	for(int i=1;i<=n;i++) ans=ans*2%mod;
    	cout<<ans<<endl;
    }
    
  • 相关阅读:
    Leetcode 493.翻转对
    Leetcode 491.递增子序列
    Leetcode 488.祖玛游戏
    Leetcode 486.预测赢家
    Leetcode 483.最小好进制
    Leetcode 482.密钥格式化
    商品期货投资的那些事(八)为你的套利头寸买个保险
    商品期货投资的那些事(七)趋势套利震荡投机、直道飘移弯道超越
    商品期货投资的那些事(五)做空焦煤焦炭?你是想跟发改委作对吗?
    商品期货投资的那些事(六)期现投资这行怎样才能赚大钱?
  • 原文地址:https://www.cnblogs.com/mikufun-hzoi-cpp/p/12147050.html
Copyright © 2011-2022 走看看