jzoj5014 【NOI2017模拟3.13】矩阵 (线性代数,矩阵计数)

题意

A,B,CA, B, CA,B,C都是n阶01方阵,给定CCC,现求有多少对有序(A,B)(A,B)(A,B)满足A∗B=CA * B = CAB=C.
乘法是模2意义下的矩阵乘法。
n≤2000n \leq2000n2000

迷得很这个题
模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)}2nr(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+kspan(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}2n2j+k种。但要区分是j+1还是k+1.
j+1的情况有2r+k−2j+k2^{r+k}-2^{j+k}2r+k2j+k,k+1的情况有2n−2r+k2^n-2^{r+k}2n2r+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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值