:::info[Description] Link. :::
:::infoSolution 先回顾一下 Hall 定理:
对一张二分图 而言,设其左部点集合为 ,右部点集合为 ,则其存在大小为 的最大匹配当且仅当 。
这里给出 Hall 定理的一个推论;
对一张二分图 ,设其左部点集合为 ,右部点集合为 ,则其最大匹配大小为 。
对一个左部点集合 ,考虑记 ,则上面的最大匹配大小可以被表示为 ,因此问题可以被转化为求 的期望值是多少。
考虑对一个二分图,记这个二分图的好集合为其左部点集合的所有子集中,满足 的集合 。然后再定义极好集合为该二分图的所有好集合中,大小最小的集合。可以证明对任意二分图,都恰好存在一个极好集合。具体的:若两个不同的集合 都是极好集合,则其交集 也必然是极好集合,而显然有 。
然后考虑 dp。设 表示只考虑 两个点集内的点,以及一端在 集合内一端在 集合内的边时,把这张图看作是一个以 集合为左部点, 集合为右部点的二分图中,若 集合的邻域恰好为 集合,而且存在一个匹配使得 集合中所有点都能匹配到 集合中的不同点的概率是多少; 表示在上面的条件下, 集合是该生成二分图的极好集合的概率是多少。
初始条件显然是 。
为了方便转移,这里记 表示在上面的条件下, 集合中的每个点都至少被 集合中某个点连到的概率是多少; 表示在上面的条件下, 集合中的每个点都无法被 集合中的任意一个点连到的概率是多少。这两个东西是好处理的。具体来说:
- 是十分容易的,每条边的贡献都是独立的,因此直接乘起来可得 。
- 也不是很困难,考虑容斥,对某个点 其不被 集合中任意一个点连到的概率为 。然后不同右端点直接也是互相独立的,因此同样可以直接相乘,得到 。
然后考虑转移 两个 dp 数组。
先考虑第一个条件: 点集的邻域恰好为 ,概率显然是 。然后考虑容斥算不合法概率,如果某张图不能把 完全匹配,则其必然满足 。取该左部点集唯一的极好集合 然后再设 是 集合的邻域,则此时一定有 。 是左部点集唯一的极好集合的概率就是 ,然后还需要保证 集合是 集合的完整邻域,所以 集合不能有边连向 ,概率就是 。删掉 两个点集后,剩下的部分 必须能完全匹配到 ,所以还需要额外乘 。
总结一下, 数组的转移就是 ,限制条件是 。
然后考虑转移 数组。同样先从邻域恰好为 的图开始,也就是 。同样考虑容斥,如果 集合不是极好集合,则说明其一定存在一个真子集 为极好集合。设 集合的邻域为 ,则为了让 不是极好集合,必须有 。确定 两个集合后,同样还是分开算贡献, 集合成为极好集合的概率为 ,然后 集合不能有边连向 ,概率为 ,剩下的 必须能够完全匹配到 所以还需要额外乘 。
总结一下, 数组的转移就是 ,不一样的地方是限制条件为 。
转移这两个数组的时候直接一起转移即可。
最后考虑怎么统计答案。设整张图的极好集合为 , 集合在整张图中的邻域集合为 ,则此时 因此该图的最大匹配大小就是 。为了保证 在整张图上的邻域恰好还是 集合,需要保证 集合不连向 集合,因此概率需要乘 。然后考虑剩下的左部点部分 ,她们必须能够值用二分图右部点中的剩余点 来完全匹配。一位内不知道具体连到了哪些剩余的右部点,因此考虑再次枚举这个邻域 。这个部分邻域恰好为 且能够完全匹配的概率是 ,同时还需要保证这些点都不连向集合 因此还需要额外乘以 。
总结一下,这一类二分图度对答案的贡献可以表示为 。枚举所有的集合 然后把所有贡献相加即可得到最终的答案。
该算法的时间复杂度为 ,可以通过该题。 :::
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int p[610][610], tmp[610][610], all[610][610], noth[610][610], cnt[610], f[610][610], g[610][610];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m; cin >> n >> m;
const int inv100 = inversion(100);
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j) cin >> p[i][j], p[i][j] = p[i][j] * inv100 % mod;
for (int i = 0; i < n; i++) {
tmp[i][0] = 1;
for (int t = 1; t < (1 << m); ++t) {
int j = __builtin_ctz(t);
tmp[i][t] = tmp[i][t ^ (1 << j)] * (1 - p[i][j] + mod) % mod;
}
}
for (int t = 0; t < (1 << m); ++t) noth[0][t] = 1;
for (int s = 1; s < (1 << n); ++s) {
int i = __builtin_ctz(s), pre = s ^ (1 << i);
for (int t = 0; t < (1 << m); ++t) noth[s][t] = noth[pre][t] * tmp[i][t] % mod;
}
for (int s = 0; s < (1 << n); ++s) {
all[s][0] = 1;
for (int t = 1; t < (1 << m); ++t) {
all[s][t] = 1;
for (int S = t; S; S = (S - 1) & t) all[s][t] = (all[s][t] + mod - all[s][t ^ S] * noth[s][S] % mod) % mod;
}
}
for (int s = 0; s < (1 << n); ++s)
for (int t = 0; t < (1 << m); ++t) f[s][t] = g[s][t] = 0;
f[0][0] = g[0][0] = 1;
for (int s = 1; s < (1 << n); ++s) {
for (int t = 0; t < (1 << m); ++t) {
if (__builtin_popcount(s) > __builtin_popcount(t)) {
g[s][t] = all[s][t];
for (int a = (s - 1) & s; ; a = (a - 1) & s) {
for (int b = t; ; b = (b - 1) & t) {
if (__builtin_popcount(a) - __builtin_popcount(b) >= __builtin_popcount(s) - __builtin_popcount(t)) g[s][t] = (g[s][t] + mod - g[a][b] * f[s ^ a][t ^ b] % mod * noth[a][t ^ b] % mod) % mod;
if (!b) break;
} if (!a) break;
}
} else {
f[s][t] = all[s][t];
for (int a = s; a; a = (a - 1) & s)
for (int b = t; ; b = (b - 1) & t) {
if (__builtin_popcount(a) > __builtin_popcount(b)) f[s][t] = (f[s][t] + mod - g[a][b] * f[s ^ a][t ^ b] % mod * noth[a][t ^ b] % mod) % mod;
if (!b) break;
}
}
}
}
int res = 0;
for (int s = 0; s < (1 << n); ++s)
for (int t = 0; t < (1 << m); ++t)
for (int t0 = (((1 << m) - 1) ^ t);; t0 = (t0 - 1) & (((1 << m) - 1) ^ t)) {
res = (res + g[s][t] * noth[s][(((1 << m) - 1) ^ t)] % mod * f[((1 << n) - 1) ^ s][t0] % mod * noth[((1 << n) - 1) ^ s][(((1 << m) - 1) ^ t) ^ t0] % mod * (n - __builtin_popcount(s) + __builtin_popcount(t)) % mod) % mod;
if (!t0) break;
}
cout << res << '\n';
}
} // namespace lowspeed_song:::
喜欢的话,留下你的评论吧~