2023.3.11 省选模拟赛 谜 (maze) 题解

题面

题目描述

舞池上有 nn 个男生与 nn 个女生.给定矩阵 M=(mi,j)n×nM=(m_{i,j})_{n \times n},若 mi,j=1m_{i,j} = 1,表示男生 ii 和女生 jj 可以配对.舞会组织者想知道有多少方案能使得每个人都有舞伴?注意公平起见,一个人不能有多个舞伴.

除了这 nn 个男生,还有 mm 个男备胎站在舞会边上.假如舞池中有男生身体不适,备胎中的男生可以把他暂时替换掉.

当然,除了这 mm 个男备胎,还有 kk 个女备胎,她们的作用也是类似的.

现在有 qq 个事件,有两类:

  1. 0 u v0\ u\ v,表示用男备胎 vv 替换掉男生 uu
  2. 1 u v1\ u\ v,表示用女备胎 vv 替换掉女生 uu

注意替换都是暂时的,过了这次事件后都会换回来.每次替换后,你都要告诉组织者合法方案数.问题是方案数可能很大,所以你只需要告诉他这个值模 22 是多少就可以了.

数据范围

n,m,k1000n,m,k \le 1000q100000q \le 100000

思路

对于一个长度为 nn 的排列 pp,我们让男生 ii 和女生 pip_i 匹配,容易发现这个匹配是完美匹配的充要条件是 i=1nMi,pi=1\displaystyle\prod_{i = 1}^{n} M_{i,p_i} = 1

考虑所有这样的排列就是考虑了所有匹配情况,所以所给二分图的完美匹配数目为:

pSni=1nMi,pi\sum_{p \in S_n} \prod_{i = 1}^{n} M_{i,p_i}

其中 SnS_n 为所有长度为 nn 的排列所构成的集合.

这是所给矩阵 MM 的积和式 perm(M)\operatorname{perm}(M).而我们知道,在这个问题中,矩阵 MM 的行列式 det(M)perm(M)(mod2)\det(M) \equiv \operatorname{perm}(M) \pmod 2.证明是平凡的,考虑二式差的奇偶性即可.

每次替换一个男生或女生,就是替换矩阵 MM 的一行或一列,所以问题转化为求原矩阵替换一行一列后的行列式.

使用 Gauss 消元求解行列式,若每次重新计算 det(M)\det(M),时间复杂度 O(qn3)O(q n^3),会 TLE.

由矩阵的 Laplace 展开可知,我们若先处理原矩阵的伴随矩阵 MM^*,那么有:

det(M)=j=1(1)i+jMi,jMi,j\det(M) = \sum_{j = 1} (-1)^{i + j} M_{i,j} {M^*}_{i,j}

注意到替换某一行或某一列时候,伴随矩阵中对应的行或列的值不会改变,所以可以 O(n3)O(n^3) 预处理伴随矩阵,O(n)O(n) 查询.

det(M)0\det(M) \not= 0 时,MM 可逆,直接使用 M=det(M)M1M^* = \det(M)M^{-1} 计算.

det(M)=0\det(M) = 0 时,MM 不可逆.设 MM 的秩为 rr,那么 MM 一定与 JJ 等价,其中 JJ 代表 rr 阶单位矩阵.也就是说存在可逆矩阵 P,QP,Q,使得 PMQ=JPMQ = J.那么有:

M=(P1JQ1)=QJPdet(P)det(Q)M^* = \left( P^{-1} J Q^{-1} \right)^* = \frac{Q J^* P}{\det(P) \det(Q)}

r<n1r < n - 1 时,JJ^* 为全 00 矩阵;当 r=n1r = n - 1 时,Jn,n=1{J^*}_{n,n} = 1,其他的元素为 00;当 r=nr = n 时,J=JJ^* = J

然而 n1000n \le 1000,预处理都跑不完.发现矩阵 MM0101 矩阵,那么相关运算都可以使用 bitset 优化.

最终时间复杂度 O(n3/w+qn/w)O(n^3 / w + qn / w)

代码

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <bitset>
#include <cstdio>

constexpr int N = 1e3;

int n;

using vec = std::bitset<N + 5>;
struct mat {
vec _m[N + 5];

vec &operator[](int i) { return _m[i]; }
const vec &operator[](int i) const { return _m[i]; }

static mat I() {
mat R;
for (int i = 1; i <= n; i++) {
R[i][i] = 1;
}
return R;
}

mat T() const {
mat R;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
R[i][j] = _m[j][i];
}
}
return R;
}

friend mat operator*(const mat &A, const mat &B) {
mat T = B.T(), R;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
R[i][j] = (A[i] & T[j]).count() & 1;
}
}
return R;
}
} A;

int gauss(mat &A, mat &R) {
R = mat::I();
int r = 0;
for (int i = 1, p = 1; i <= n; i++) {
if (!A[p][i]) {
for (int j = p + 1; j <= n; j++) {
if (A[j][i]) {
std::swap(A[p], A[j]);
std::swap(R[p], R[j]);
break;
}
}
}
if (!A[p][i]) continue;
for (int j = 1; j <= n; j++) {
if (j != p && A[j][i]) {
A[j] ^= A[p], R[j] ^= R[p];
}
}
p += 1, r += 1;
}
return r;
}

mat adj, adjt;
void getadj(mat A) {
mat P, Q;
int r = gauss(A, P);
A = A.T(), gauss(A, Q), Q = Q.T();

if (r < n - 1) return;
else if (r == n - 1) adj[n][n] = 1;
else adj = mat::I();

adj = Q * adj * P;
adjt = adj.T();
}

int m, k, q;
vec chi[N + 5], chj[N + 5];

char buf[N + 5];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s", buf + 1);
for (int j = 1; j <= n; j++) {
A[i][j] = buf[j] - '0';
}
}
scanf("%d%d", &m, &k);
for (int i = 1; i <= m; i++) {
scanf("%s", buf + 1);
for (int j = 1; j <= n; j++) {
chi[i][j] = buf[j] - '0';
}
}
for (int i = 1; i <= k; i++) {
scanf("%s", buf + 1);
for (int j = 1; j <= n; j++) {
chj[i][j] = buf[j] - '0';
}
}

getadj(A);
printf("%d\n", (int)(adjt[1] & A[1]).count() & 1);

scanf("%d", &q);
for (int i = 1, opt, u, v; i <= q; i++) {
scanf("%d%d%d", &opt, &u, &v);
if (!opt) printf("%d\n", (int)(adjt[u] & chi[v]).count() & 1);
else printf("%d\n", (int)(adj[u] & chj[v]).count() & 1);
}

return 0;
}