首先有 暴力就是枚举排列算概率,还有就是直接状压 表示左边选到 右边集合为 的概率
想一想,最后求的是什么,就是我们钦定一些边必须选使得有完美匹配,然后将这些边一起出现的概率乘起来作为贡献
把 1 类边和 2 类边都拆成 0 类边
考虑若最后钦定的边中 1 类的两条同时出现,那么它们两个的贡献是 ,而本应该是 ,所以我们添加一个两条边出现概率为 的捆绑来平衡概率
如果这两条边在某一次钦定中同时存在了,那么我们的捆绑肯定也会在某一次钦定中存在
如果钦定的边中只有其中 1 条,那么它对概率的贡献是 ,本身就是
于是乎概率就正确了, 2 类边添加概率为 的捆绑即可
发现这样并不能刚刚那样状压
我们令 为两边集合为 的概率,每次枚举最小的不在 中的转移即可,复杂度
感觉拆边新增一个捆绑边来平衡概率挺巧妙的
另外了解了枚举最小不在集合中的点来转移的方法
扫描二维码关注公众号,回复:
8675838 查看本文章
#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;
}