• 「THUWC 2017」随机二分图(概率+容斥+状压dp(记忆化搜索实现))


    https://loj.ac/problem/2290

    先看(O(n!))的怎么做?

    枚举一个排列(完美匹配),计算这个匹配的边一定出现(其它边随意)的概率。

    若组0、1、2有恰好一条边在这个匹配,则概率(*1/2)
    若组1有恰好两条边,则概率(*1/2)
    若组2有恰好两条边,则概率(*0)

    考虑只有组0的情况,显然设(f[i][S])表示左边确立了前i个,右边选了S,的概率和,就好了。

    加入组1,把它拆成两条 组0的边,发现会少计算1/4的两条边同时选,加上这个。

    加入组2,把它拆成两条 组0的边,发现会多计算1/4的两个边同时选,减去这个。

    然后设一个(f[S1][S2])表示左边选了S1,右边选了S2的概率和。

    需要确立一个转移顺序,也就是每次转移必须使S1的最低的0变成1.

    状态上限是(sum_{i=0}^{n}C(n,i)^2=C(2n,n)≈1.5*10^8),加上转移复杂度(O(n))TLE了。

    发现这个状态跑不满,用记忆化+hash表转移,状态数不太会证。


    Code:

    #include<bits/stdc++.h>
    #define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
    #define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
    #define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
    #define ll long long
    #define pp printf
    #define hh pp("
    ")
    using namespace std;
    
    const int mo = 1e9 + 7;
    
    ll ksm(ll x, ll y) {
    	ll s = 1;
    	for(; y; y /= 2, x = x * x % mo)
    		if(y & 1) s = s * x % mo;
    	return s;
    }
    
    const int N = 16;
    
    ll ni2 = ksm(2, mo - 2), ni4 = ksm(4, mo - 2);
    int n, m;
    
    int b[N][N];
    ll c[N][N][4], c0[N];
    
    map<int, ll> f;
    
    ll calc(int s1, int s2) {
    	if(s1 == s2 && s1 == (1 << n) - 1) return 1;
    	int s3 = s1 + (s2 << n);
    	if(f.count(s3)) return f[s3];
    	ll ans = 0;
    	ff(i, 0, n) if(!(s1 >> i & 1)) {
    		ff(j, 0, n) if(b[i][j] && !(s2 >> j & 1)) {
    			ans = (ans + calc(s1 | (1 << i), s2 | (1 << j)) * ni2) % mo;
    		}
    		fo(j, 1, c0[i]) {
    			if(!(s2 >> c[i][j][0] & 1) && !(s1 >> c[i][j][1] & 1) && !(s2 >> c[i][j][2] & 1))
    				ans = (ans + calc(s1 | (1 << i) | (1 << c[i][j][1]), s2 | (1 << c[i][j][0]) | (1 << c[i][j][2])) * c[i][j][3]) % mo;
    		}
    		break;
    	}
    	return f[s3] = ans;
    }
    
    int main() {
    	scanf("%d %d", &n, &m);
    	fo(i, 1, m) {
    		int t, a1, b1, a2, b2;
    		scanf("%d %d %d", &t, &a1, &b1);
    		a1 --; b1 --;
    		if(t == 0) {
    			b[a1][b1] = 1;
    		} else {
    			scanf("%d %d", &a2, &b2);
    			a2 --; b2 --;
    			if(a1 > a2) swap(a1, a2), swap(b1, b2);
    			b[a1][b1] = 1;
    			b[a2][b2] = 1;
    			if(a1 == a2 || b1 == b2) continue;
    			c0[a1] ++;
    			c[a1][c0[a1]][0] = b1;
    			c[a1][c0[a1]][1] = a2;
    			c[a1][c0[a1]][2] = b2;
    			c[a1][c0[a1]][3] = t == 1 ? ni4 : mo - ni4;
    		}
    	}
    	ll ans = calc(0, 0);
    	fo(i, 1, n) ans = ans * 2 % mo;
    	pp("%lld
    ", ans);
    }
    
  • 相关阅读:
    如何学习go源码
    最近打仁王2
    如何在时间复杂度为O(n)空间复杂度为O(1)的情况下完成链表的逆置
    漏洞复现-shellshock-bash破壳
    漏洞复现-aria2-任意文件写入
    漏洞复现-apereo-cas-4.1-rce
    漏洞复现-cgi-httpoxy
    漏洞复现-weblogic_weak_password-getshell
    sqlserver2008安装教程
    mysql内一些可以延时注入的查询语句
  • 原文地址:https://www.cnblogs.com/coldchair/p/12628799.html
Copyright © 2020-2023  润新知