「THUWC 2017」随机二分图(状压DP)(概率DP)

传送门

首先有 40 p t s 40pts 40pts 暴力就是枚举排列算概率,还有就是直接状压 f [ i ] [ S ] f[i][S] f[i][S] 表示左边选到 i i i 右边集合为 S S S 的概率

想一想,最后求的是什么,就是我们钦定一些边必须选使得有完美匹配,然后将这些边一起出现的概率乘起来作为贡献

把 1 类边和 2 类边都拆成 0 类边

考虑若最后钦定的边中 1 类的两条同时出现,那么它们两个的贡献是 1 / 4 1/4 1/4,而本应该是 1 / 2 1/2 1/2,所以我们添加一个两条边出现概率为 1 / 4 1/4 1/4 的捆绑来平衡概率

如果这两条边在某一次钦定中同时存在了,那么我们的捆绑肯定也会在某一次钦定中存在

如果钦定的边中只有其中 1 条,那么它对概率的贡献是 1 / 2 1/2 1/2,本身就是 1 / 2 1/2 1/2

于是乎概率就正确了, 2 类边添加概率为 − 1 / 4 -1/4 1/4 的捆绑即可

发现这样并不能刚刚那样状压

我们令 f [ S 1 ] [ S 2 ] f[S_1][S_2] f[S1][S2] 为两边集合为 S 1 , S 2 S_1,S_2 S1,S2 的概率,每次枚举最小的不在 S 1 S_1 S1 中的转移即可,复杂度 O ( n 2 2 n ) O(n^22^n) O(n22n)

感觉拆边新增一个捆绑边来平衡概率挺巧妙的
另外了解了枚举最小不在集合中的点来转移的方法

#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int N = 16, M = N*N*3;
cs int Mod = 1e9 + 7, inv2 = (Mod+1)>>1, inv4 = (Mod+1)>>2;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
int mul(int a, int b){ return 1ll * a * b % Mod; }
void Add(int &a, int b){ a = add(a, b); }
int ksm(int a, int b){int ans=1; for(;b;b>>=1,a=mul(a,a)) if(b&1) ans=mul(ans,a); return ans;}
int n, m, a[M], b[M], ct, all;
unordered_map<int, int> f;
int dp(int S){
	if(S == all) return 1;
	if(f.count(S)) return f[S];
	int nxt = -1;
	for(int i = 0; i < n; i++) if(!(S & (1<<i))){ nxt = 1<<i; break; }
	int ret = 0;
	for(int i = 1; i <= ct; i++)
		if((nxt & a[i]) && !(S & a[i]))
			Add(ret, mul(b[i], dp(S | a[i])));
	return f[S] = ret;
}
int main(){
	n = read(), m = read(); all = (1<<n+n) - 1;
	for(int i = 1; i <= m; i++){
		int op = read(), x = read()-1, y = read()-1, S = 0;
		a[++ct] = S = (1<<x)|(1<<(y+n)); b[ct] = inv2;
		if(op){
			x = read()-1, y = read()-1; 
			a[++ct] = (1<<x)|(1<<(y+n)); b[ct] = inv2;
			if(S & ((1<<x)|(1<<y+n))) continue;
			a[++ct] = S | ((1<<x)|(1<<y+n)); b[ct] = op == 1 ? inv4 : Mod-inv4;
		}
	} cout << mul(dp(0), ksm(2,n)); return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值