题意
A,B,CA, B, CA,B,C都是n阶01方阵,给定CCC,现求有多少对有序(A,B)(A,B)(A,B)满足A∗B=CA * B = CA∗B=C.
乘法是模2意义下的矩阵乘法。
n≤2000n \leq2000n≤2000
?
迷得很这个题
模2意义就相当于加法是xor,数量乘法是and,仍然能组成一个线性空间。
因此下面的讨论是在数域0,1数域{0,1}数域0,1下的进行的。
首先要发现一下,AB=C可以视作是将A的列向量进行n次线性组合得到c的每个列向量。(每一次的系数就是B矩阵对应列)。因此,c的张成是a的张成的一个线性子空间。
O(n3)O(n^3)O(n3)
考虑枚举A,那么计算B的方案数。
假如A的秩记作r(A)r(A)r(A)(也就是其列向量张成的维数,或者理解成线性基大小都可以)
即每一列有2n−r(A)2^{n-r(A)}2n−r(A)种取值方案。
求矩阵的秩可以高斯消元,bitset优化。
接下来考虑一个dp去构造矩阵A并计算贡献。
设f(i,j,k)f(i,j,k)f(i,j,k)表示已经构造了1…i列,span(A)span(A)span(A)的维数是j+kj+kj+k。span(A)span(A)span(A)与span(C)span(C)span(C)的交的维数为jjj.
考虑新的一列有什么情况。要么A的维数保持不变,那么就有2j+k2^{j+k}2j+k种情况。
要么A的维数加一,这里的情况总数有2n−2j+k2^n-2^{j+k}2n−2j+k种。但要区分是j+1还是k+1.
j+1的情况有2r+k−2j+k2^{r+k}-2^{j+k}2r+k−2j+k,k+1的情况有2n−2r+k2^n-2^{r+k}2n−2r+k,其中r是C的张成维数。
原因大概是,在这个线性空间里k维的子空间点数是2k2^k2k,再根据各个空间的包含关系即可得出。
限于知识水平我无法在这里给出很清晰的解释,各位看官请在评论区指教。
最终的答案就可以非常简单的计算了。这个dp是O(n3)O(n^3)O(n3)的,但这个做法启示我们,答案事实上只与r(C)r(C)r(C)与n有关。
进一步观察
一步非常妙的转化,既然给定r(C)r(C)r(C)求答案比较复杂,我们可以计算出所有秩相同的矩阵的答案,再除以个数。
设F[i][j]F[i][j]F[i][j]是确定了前i列,张成的维数为j的矩形个数,完成这个dp只需要O(n2)O(n^2)O(n2)的复杂度。
考虑一个秩为x的矩形作为A(x>=r(C))(x >= r(C))(x>=r(C)),其对应的C的每一列向量都可以表示为长度为x的01向量(即span(A)的线性组合)。C的秩就是这个n * x矩形的秩,于是这样的C有F[x][r(C)]F[x][r(C)]F[x][r(C)]种。答案也就可以计算了。
复杂度O(n3/w+n2)O(n^3/w+n^2)O(n3/w+n2)。
#include <bits/stdc++.h>
using namespace std;
const int N = 2020, mo = 1e9 + 7;
typedef long long ll;
bitset<N> c[N];
int n, r;
void write() {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) printf("%d",(int)c[i][j]);
printf("\n");
}
}
void getrank() {
int used = 0;
for(int i = 1; i <= n; i++) {
// write(); printf("\n");
int can = 0;
for(int j = used + 1; j <= n; j++) if (c[j][i]) {
can = 1;
used++;
swap(c[used], c[j]);
break;
}
if(can)
r++;
else
continue;
for(int j = used + 1; j <= n; j++) if(c[j][i]) {
c[j] ^= c[used];
}
}
}
ll f[N][N];
ll mi[N];
ll ksm(ll x,ll y) {
ll ret = 1; for (; y; y >>= 1) {
if (y & 1) ret = ret * x % mo;
x = x * x % mo;
}
return ret;
}
int main() {
freopen("mat.in","r",stdin);
// freopen("mat.out","w",stdout);
cin>>n;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
int w; scanf("%d", &w);
if(w) c[i].set(j);
}
}
getrank();
mi[0] = 1; for(int i = 1; i <= n; i++) mi[i] = mi[i - 1] * 2 % mo;
f[0][0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = 0; j < i; j++) if (f[i - 1][j]) {
f[i][j] = (f[i][j] + f[i - 1][j] * mi[j]) % mo;
f[i][j + 1] = (f[i][j + 1] + f[i - 1][j] * (mi[n] - mi[j])) % mo;
}
}
ll ans = 0;
for(int x = r; x <= n; x++) {
ans = (ans + f[n][x] * f[x][r] % mo * ksm(2, n * (n - x))) % mo;
}
ans = ans * ksm(f[n][r], mo - 2) % mo;
cout<<(ans+mo)%mo<<endl;
}