@description@

一个左右各 n 个点的二分图,图中的边会按照一定的规律随机出现。将这些边分到若干个组中(每条边至多属于一个组):

第(1)类组每组有一条边,该边有 50% 的概率出现。

第(2)类组每组有两条边,这两条边有 50% 的概率同时出现,有 50% 的概率同时不出现。

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

问完美匹配数量的期望。

input

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

接下来 m 行,每行描述一个组。开头第一个数 t 表示组的种类,t = 0 表示是第(1)类组,t = 1 表示是第(2)类组,t = 2 表示是第(3)类组。如果 t=0, 接下来两个数 a1, b1 表示组内的边 (a1, b1);否则,接下来四个数 a1, b1, a2, b2 表示该组内的两条边分别为 (a1, b1) 和 (a2, b2)。保证每条边至多出现一次。

output

假设期望的完美匹配数量是 E。输出 \((2^{n} E) \bmod (10^9 + 7)\)

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

sample input

2 2

1 2 1 2 2点

2 1 2 1 1

sample output

2

@solution@

【说实话读完题目感觉整个人有些晕】

我们注意到题目中有这样一个句话:\((2^{n} E)\) 一定是整数。

为什么?我们考虑每一种完美匹配对答案的贡献。

假如我选用了 k 个组里面的边,凑出来了一个完美匹配。则剩下的组随便怎么出现,这一个完美匹配都会对答案产生贡献。也就是说,这一个完美匹配会让总期望增加 \(\frac{2^{m-k}}{2^m} = \frac{1}{2^k}\)。

可以发现 k <= n,因为完美匹配只需要 n 条边,而每个组最少都有 1 条边。

这样子,我们就可以完全抛开原本的概率期望,把这道题转换为组合计数问题。

对于 t = 0,因为每一组只会有一条边,所以我始终会选用 n 个组里面的边,相当于是统计完美匹配的个数。

这是一个很经典的二分图状压 dp。

对于 t = 1,假如最终的完美匹配只包含其中的一条边,则完全不影响;如果最终的完美匹配包含其中的两条边,它对答案的贡献就变成两倍。我们正常情况下已经贡献了一倍,则我们再进行一次特殊的转移,强制两条边一起选即可。

注意这个地方的状态定义为:\(dp[s1][s2]\) 表示二分图左边的状态为 s1,右边的状态。理论来说状态数非常大(155117520,这是在保证左右状态已经使用的点的数量相同下得到的理论状态数量),但是实测并不会用到多少。用记忆化搜索就可以避免无用状态。

对于 t = 2,我们容斥一下,用总的减去最终答案包含两条边的贡献。其实就是 t = 1 减去强制两条边一起选的贡献。

@accepted code@

#include<vector>
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN = 15;
const int MAXM = MAXN*MAXN;
const int MOD = int(1E9) + 7;
const int HASHSIZE = 1000037;
typedef pair<int, int> pii;
vector<pair<pii, int> >h[HASHSIZE];
void hash_insert(pii p, int k) {
int x = (1LL*p.first*13131%HASHSIZE + p.second)%HASHSIZE;
h[x].push_back(make_pair(p, k));
}
int hash_search(pii p) {
int x = (1LL*p.first*13131%HASHSIZE + p.second)%HASHSIZE;
for(int i=0;i<h[x].size();i++)
if( h[x][i].first == p ) return h[x][i].second;
return -1;
}
struct node{
int type, from, to;
node *lnk, *nxt;
}e[2*MAXM + 5], *adj[MAXN + 5], *ecnt=&e[0];
void add(int u, int v, int t, node *l) {
node *p = (++ecnt);
p->type = t, p->from = u, p->to = v, p->lnk = l;
p->nxt = adj[u], adj[u] = p;
}
int f[1<<MAXN];
int lowbit(int x) {
return x & -x;
}
int dfs(int s1, int s2) {
if( !s1 ) return 1;
pii p = make_pair(s1, s2);
int q = hash_search(p);
if( q != -1 ) return q;
else {
int k = f[lowbit(s1)], ans = 0;
for(node *a=adj[k];a;a=a->nxt) {
if( !a->type ) {
if( s2 & (1<<a->to) )
ans = (ans + dfs(s1^(1<<a->from), s2^(1<<a->to))) % MOD;
}
else {
node *b = a->lnk;
if( (s2 & (1<<a->to)) && (s2 & (1<<b->to)) )
ans = (ans + a->type*dfs(s1^(1<<a->from)^(1<<b->from), s2^(1<<a->to)^(1<<b->to)))%MOD;
}
}
hash_insert(p, ans);
return ans;
}
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i=0;i<n;i++)
f[1<<i] = i;
for(int i=1;i<=m;i++) {
int t, a1, b1, a2, b2;
scanf("%d", &t);
if( !t ) {
scanf("%d%d", &a1, &b1); a1--, b1--;
add(a1, b1, 0, NULL);
}
else {
scanf("%d%d%d%d", &a1, &b1, &a2, &b2); a1--, b1--, a2--, b2--;
add(a1, b1, 0, NULL); add(a2, b2, 0, NULL);
if( a1 != a2 && b1 != b2 ) {//注意如果相等,则两条边是不能够共存的。
add(a1, b1, t == 1 ? 1 : -1, ecnt + 2);
add(a2, b2, t == 1 ? 1 : -1, ecnt);
}
}
}
printf("%d\n", (dfs((1<<n)-1, (1<<n)-1) + MOD)%MOD);
}

@details@

为什么他们写 map 不写哈希能够过而我就过不了呢……

难道真的是因为我自带常数吗?

其实本代码在某个辣鸡 OJ 还是 TLE。

如果不说鬼才知道状态数原来很少。

05-26 02:31