题解:CF1210F1 Marek and Matching (easy version)

· · 题解

这个做法也能过 Hard Version 的。

根本不会,严肃学习,肃然起敬,该加训了。

考虑用 Hall 定理刻画。对于 S, T 如果没有完美匹配,考虑其中 S' \subseteq S 作为非法的代表元,满足 |S'| - |N(S')| 最大,同时 |S'| 最小。

这样的代表元一定唯一,证明:

如果不唯一,即两个 S_1, S_2 满足上述条件。如果它们无交,那么有 S_1\cup S_2 显然更优,如果它们有交,那么就有 |S_1| - |N(S_1)| = |S_2| - |N(S_2)|, |S_1| - |N(S_1)| + |S_2| - |N(S_2)| = |S_1 \cup S_2| - |N(S_1 \cup S_2)| + |S_1 \cap S_2| - |N(S_1 \cap S_2)|,于是发现要么然 |S_1 \cap S_2| 一样大且更小,要么然两个中必然有一个更大。

f[S, T] 为对于 SN(S) = T 拥有完美匹配的概率,g[S, T] 为对于 S 内部所有点 S 即为非法代表元的概率。

直接转移非常难转移,因为代表元并不具有子结构。于是正难则反,对于 g[S, T] 考虑 S 并不是代表元的情况,对于 f[S, T] 考虑存在代表元的情况。两种讨论其实很类似,都是考虑 S'\subseteq S, T' \subseteq T(S', T') 作为代表元。如果 (S', T')(S, T) 的代表元,那么概率就是 g[S', T']\times f[S - S', T - T']\times no_{S', T - T'}

解释一下:

转移就是用 $to_{S, T}$ 代表 $S$ 的邻居集 $N(S) = T$ 的概率,减去上面的不合法的概率。枚举子集即可,时间复杂度 $O(3^{2n})$。 ```cpp #include <bits/stdc++.h> using namespace std; const int N = 8, Mod = 1e9 + 7; int p[N + 3][N + 3], n; int to[(1 << N) + 3][(1 << N) + 3], no[(1 << N) + 3][(1 << N) + 3]; int f[(1 << N) + 3][(1 << N) + 3], g[(1 << N) + 3][(1 << N) + 3]; int popc[(1 << N) + 3]; void upd(int &x, int y) { x = ((x + y >= Mod) ? (x + y - Mod) : (x + y)); } int qpow(int n, int m) { int res = 1; while(m) { if(m & 1) res = 1ll * res * n % Mod; n = 1ll * n * n % Mod; m >>= 1; } return res; } int inv100 = qpow(100, Mod - 2); void init() { cin >> n; for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) cin >> p[i][j], p[i][j] = 1ll * p[i][j] * inv100 % Mod; for(int S = 0; S < (1 << n); S++) { for(int T = 0; T < (1 << n); T++) { to[S][T] = no[S][T] = 1; for(int i = 0; i < n; i++) { int res = 1; if(!((T >> i) & 1)) continue; for(int j = 0; j < n; j++) if((S >> j) & 1) res = 1ll * res * (Mod + 1 - p[j][i]) % Mod; to[S][T] = 1ll * to[S][T] * (Mod + 1 - res) % Mod; } for(int i = 0; i < n; i++) { int res = 1; if(!((S >> i) & 1)) continue; for(int j = 0; j < n; j++) if((T >> j) & 1) res = 1ll * res * (Mod + 1 - p[i][j]) % Mod; no[S][T] = 1ll * no[S][T] * res % Mod; } } } for(int S = 0; S < (1 << n); S++) for(int T = 0; T < (1 << n); T++) f[S][T] = g[S][T] = 0; for(int S = 0; S < (1 << n); S++) { for(int T = 0; T < (1 << n); T++) { if(!(S + T)) continue; g[S][T] = f[S][T] = to[S][T]; if(popc[S] <= popc[T]) g[S][T] = 0; for(int SS = S; ; SS = (SS - 1) & S) { for(int TT = T; ; TT = (TT - 1) & T) { if(popc[S] > popc[T] && popc[SS] - popc[TT] >= popc[S] - popc[T]) upd(g[S][T], Mod - 1ll * g[SS][TT] * no[SS][T - TT] % Mod * f[S - SS][T - TT] % Mod);//如果 (SS, TT) 是更加优秀的代表元,减去这部分 if(popc[S] <= popc[T]) upd(f[S][T], Mod - 1ll * g[SS][TT] * no[SS][T - TT] % Mod * f[S - SS][T - TT] % Mod);//存在代表元就减去 if(!TT) break; } if(!SS) break; } } } cout << f[(1 << n) - 1][(1 << n) - 1] << '\n'; } int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); for(int i = 0; i < (1 << N); i++) popc[i] = __builtin_popcount(i); // int T; cin >> T; // while(T--) init(); } ```