题目链接
给定一个两边各有 n 个点的二分图,判断完美匹配的个数是否是 4 的倍数。
n≤300
完美匹配的个数即积和式。
来自论文的算法:求积和式模 4 的余数。
积和式定义:考虑所有选 n 个不同行、不同列的数的方案,对每个方案中 n 个数的乘积求和。
permA=p∑Ai,pi
当 A 是 01 矩阵时,有
premA=(−1)nx∈{0,1}n∑(−1)x1+x2+⋯+xni=1∏n(Ax)i
证明可以考虑容斥:钦定某些列没有数被选。
观察到式子中间有一个 ∏ ,由于我们要求这个东西模 4 的余数,如果它要造成贡献的话,Ax 就至多有一个位置模 2 为 0。
考虑 Ax 每一项模 2 的余数,由于至多只能有一个 0,因此可以枚举这个东西的取值,它只有 n+1 种。
对于每种取值,通过高斯消元解出满足条件的所有 x,再将每一组 x 代入刚刚的式子中求出答案。
问题是,合法的 x 的个数可能很大,因为需要枚举自由元的取值。
对这个矩阵做一些变换,记为 A′。
⎣⎢⎢⎢⎢⎢⎢⎡A1,1A2,1⋮An,10A1,2A2,2An,20⋯⋯⋯⋯A1,nA2,nAn,n0v1v2⋮vn1⎦⎥⎥⎥⎥⎥⎥⎤
显然 permA′=permA。
随机选取 v,则期望 O(1) 组解:一组 x 要有贡献,xn+1=1(注意这一点,不能枚举 xn+1=0,否则复杂度会假),因此翻转 v 中的任意一位,A′x 也会翻转对应的一位,所以对于一个确定的 A′x,每一组 x 成为它的解的概率都是 2n1。
直接写复杂度是 O(n4),使用 bitset
优化,复杂度为 O(ωn4)。
笔者想出了一个复杂度更好的算法。
引理:当 n 阶方阵 A 在 模 2 意义下的秩小于等于 n−3 时(换句话就是模 2 意义下高斯消元后有至少 3 个自由元),有 permA≡0(mod4)。
证明留给读者。
把这种情况判掉就不需要随机化了,因为至多有 2 个自由元,也就至多有 4 个解。
之前算法的瓶颈在于枚举 Ax 后需要 O(n3) 求解 x 并计算贡献,这可以分为四个部分:
- 高斯消元成上三角矩阵。
- 判断是否有解(系数全 0 行的常数项是否为 0)。
- 枚举每个自由元的取值,并算出每个非自由元。
- 把 x 代入容斥式子计算贡献。
注意每次高斯消元的矩阵都是 A,只有常数项不同,因此可以考虑像矩阵求逆一样在 A 右边放一个单位矩阵进行一次消元,之后就可以 O(n2) 地由初始常数项直接计算消元后的常数项。
后三个部分的复杂度都不高于 O(n2),对于单个 Ax 就可以做到 O(n2) 的复杂度,总复杂度 O(n3)。
除高斯消元之外的部分(计算每个 Ax 的贡献)还可以优化,虽然改变不了复杂度,但实际速度可以快很多倍。
实际上并不需要每次 O(n2) 计算消元后的常数项。考虑翻转 (Ax)i,那么常数项中和 (Ax)i 有关的位也会翻转,我们要求解的 Ax 都是可以通过全 1 翻转至多一位得到的,预处理 Ax 全 1 的常数项,就可以 O(n) 得到其他 Ax 的常数项。
判断是否有解直接判断即可,由于至多有 2 个自由元,复杂度 O(1)。
求解 x 也不需要先枚举每个自由元的取值,再算出每个非自由元。考虑消元后的常数项翻转了一些位,如果保持自由元取值不变,x 也就是翻转了对应的位,在维护常数项的时候顺便维护 x 是 O(n) 的。
把 x 代入容斥式子计算贡献是 O(n2) 的,但是我们只关心贡献模 4,如果 (Ax)i≡0(mod2),那么 ∑x∈{0,1}n(−1)x1+x2+⋯+xn∏i=1n(Ax)i 就必然是偶数了,只需要知道它是否是 4 的倍数,检验一下 (Ax)i 即可。最后对 Ax 全 1 暴力计算即可。
使用 bitset
优化,复杂度 O(ωn3),除了读入和高斯消元外,其他部分复杂度不高于 O(ωn2)。
当计算积和式模 8 时,其他部分复杂度达到 O(ωn3),总复杂度仍然是 O(ωn3)!
O(ωn4) 代码:
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <bits/stdc++.h> #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define mem(a, b) memset(a, b, sizeof a) #define For(i, l, r) for(int i = (l), i##e = (r); i < i##e; i++) #define pb push_back #pragma GCC target("popcnt")
using namespace std;
const int N = 305;
int T, n; char s[N], ans; bitset<320> A[N], bas[N], x;
void dfs(int i) { if(i < 0) { char prod = 1; rep(j, 0, n) prod *= (A[j] & x).count(); ans += x.count() & 1 ? -prod : prod; } else if(bas[i][i]) dfs(i - 1); else rep(j, 0, 1) { x[i] = j, dfs(i - 1); rep(k, 0, i) x[k] = x[k] ^ bas[k][i]; } } void calc() { rep(i, 0, n) bas[i].reset(); rep(i, 0, n) { auto nw = A[i]; rep(j, 0, n) if(nw[j]) { if(bas[j][j]) nw ^= bas[j]; else { bas[j] = nw; break; } } if(nw[n + 1] && nw.count() == 1) return; } per(i, n, 0) if(bas[i][i]) rep(j, i + 1, n) if(bas[j][j] & bas[i][j]) bas[i] ^= bas[j]; rep(i, 0, n) x[i] = bas[i][n + 1]; dfs(n); } void solve() { cin >> n, ans = 0, x.reset(); rep(i, 0, n) A[i].reset(); For(i, 0, n) { cin >> s, A[i][n] = rand() & 1, A[i][n + 1] = 1; For(j, 0, n) A[i][j] = s[j] - 48; } A[n][n] = A[n][n + 1] = 1; rep(i, 0, n) A[i][n + 1] = i == n, calc(), A[i][n + 1] = 1; cout << (ans & 3 ? "NO\n" : "YES\n"); } int main() { cin.tie(0)->sync_with_stdio(0); for(cin >> T; T--; solve()); }
|
O(ωn3) 代码:
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #include <bits/stdc++.h> #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define mem(a, b) memset(a, b, sizeof a) #define For(i, l, r) for(int i = (l), i##e = (r); i < i##e; i++) #define pb push_back #pragma GCC target("popcnt")
using namespace std;
const int N = 305;
int T, n, idx; char ans, s[N], x[N]; bitset<640> a[N], bas[N], fre[3], sol[5], X;
void dfs(int i) { if(!i) { sol[++idx].reset(); rep(i, 0, n) sol[idx][i] = x[i]; } else if(bas[i][i]) dfs(i - 1); else { x[i] = 0, dfs(i - 1); rep(k, 0, i) if(bas[k][i]) x[k] ^= 1; x[i] = 1, dfs(i - 1); rep(k, 0, i) if(bas[k][i]) x[k] ^= 1; } } void solve() { cin >> n; rep(i, 1, n) a[i].reset(), bas[i].reset(); rep(i, 1, n) { cin >> s, a[i][n + i] = a[i][n + n + 2] = 1; rep(j, 1, n) a[i][j] = s[j - 1] - 48; } int p = 0; rep(i, 1, n) { auto nw = a[i]; rep(j, 1, n) if(nw[j]) { if(bas[j][j]) nw ^= bas[j]; else { bas[j] = nw; goto L; } } if(p == 2) return cout << "YES\n", void(); fre[++p] = nw; L:; } if(p == 0) return cout << "NO\n", void(); per(i, n, 1) if(bas[i][i]) rep(j, i + 1, n) if(bas[j][j] & bas[i][j]) bas[i] ^= bas[j]; rep(i, 1, n) x[i] = bas[i][n + n + 2]; idx = 0, X.reset(), ans = 0, dfs(n); bitset<640> y; rep(k, n + 1, n + n + 1) { bool ban = 0; rep(i, 1, p) ban |= fre[i][k] != fre[i][n + n + 2]; if(ban) continue; rep(i, 1, n) y[i] = bas[i][k]; rep(i, 1, idx) { X = sol[i] ^ y; if(k > n + n) { char prod = 1; rep(j, 1, n) prod *= (a[j] & X).count(); ans += X.count() & 1 ? -prod : prod; } else ans += (a[k - n] & X).count(); } } cout << (ans & 3 ? "NO\n" : "YES\n"); } int main() { cin.tie(0)->sync_with_stdio(0); for(cin >> T; T--; solve()); }
|