【BZOJ3243】【UOJ121】【NOI2013】向量内积

本文探讨了一种特殊条件下寻找矩阵中特定元素的方法。针对模意义下的矩阵乘法问题,通过构造辅助矩阵并利用随机化技巧,在多项式时间内解决该问题。适用于k=2或k=3的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目链接】

【思路要点】

  • 首先我们来考虑\(k=2\)的情况。
  • 将所有向量拼成一个\(N\)行\(D\)列的矩阵\(A\),那么问题等价于找到\(A*A^{T}\)中的一个不在主对角线上的0,或者判断不存在这样的0。
  • 显然,直接计算\(A*A^{T}\)会超时。
  • 注意到如果找不到这样的零,\(A*A^{T}\)不在主对角线上的元素应该都是1,设矩阵\(B\)是一个\(N\)行\(N\)列的矩阵,它主对角线上的元素与\(A*A^{T}\)相同,其余元素都为1,我们希望知道\(A*A^{T}\)与\(B\)是否有不同,如果有,我们希望知道不同点处的至少一维坐标(另一维可以暴力求解)。
  • 假设\(A*A^{T}=B\),那么根据矩阵乘法的性质,应当有\(X*A*A^{T}=X*B\),其中\(X\)是一个1行\(N\)列的矩阵。
  • \(X*A*A^{T}\)是容易在\(O(ND)\)的时间里求解的,由于\(B\)中只有\(O(N)\)个1,\(X*B\)是容易在\(O(N)\)的时间里求解的。
  • 但满足\(X*A*A^{T}=X*B\)时,\(A*A^{T}=B\)是不一定成立的,因为所有的运算是在取模意义下进行的,因此,就像是哈希的模数一样,我们可以随机多个矩阵来判断\(A*A^{T}=B\)是否成立。
  • 一旦发现\(A*A^{T}\)与\(B\)不相等,不相等的坐标处就是我们需要的一维坐标。
  • 当\(k=3\)时,\(B\)中不在主对角线上的数可能是1或者2,难以直接建立矩阵。
  • 注意到\(2^2\equiv 1^2\equiv 1\ (Mod\ 3)\),设新矩阵\(C\),满足\(C_{i,j}=B_{i,j}^2\),\(C\)矩阵是容易确定的。
  • \(C_{i,j}=(\sum_{k=1}^{D}A_{i,k}A^{T}_{k,j})^2=\sum_{k=1}^{D}\sum_{l=1}^{D}A_{i,k}A_{i,l}*A^{T}_{k,j}A^{T}_{l,j}\)
  • 把上面的\((k,l)\)看做是矩阵的下标,这就也可以看成矩阵乘法的形式了。
  • 时间复杂度\(O(CntND^{k-1})\),其中\(Cnt\)为尝试随机次数。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXM = 105;
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, P;
int x[MAXN], y[MAXM][MAXM], z[MAXN], d[MAXN];
int a[MAXN][MAXM], b[MAXM][MAXN], dot[MAXN];
bool solve(int x) {
	for (int i = 1; i <= n; i++) {
		if (x == i) continue;
		int ans = 0;
		for (int j = 1; j <= m; j++)
			ans += a[x][j] * a[i][j];
		if (ans % P == 0) {
			if (i > x) printf("%d %d\n", x, i);
			else printf("%d %d\n", i, x);
			return true;
		}
	}
	return false;
}
int main() {
	read(n), read(m), read(P);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		read(a[i][j]);
		a[i][j] %= P;
		b[j][i] = a[i][j];
	}
	for (int i = 1; i <= n; i++) {
		int ans = 0;
		for (int j = 1; j <= m; j++)
			ans += a[i][j] * a[i][j];
		dot[i] = ans % P != 0;
	}
	if (n <= 1e3) {
		for (int i = 1; i <= n; i++)
			if (solve(i)) return 0;
		printf("-1 -1\n");
		return 0;
	}
	srand(1840);
	int cnt = 4;
	while (cnt--) {
		int sum = 0;
		for (int i = 1; i <= n; i++) {
			x[i] = rand() % 5 != 0;
			sum += x[i];
		}
		for (int i = 1; i <= n; i++)
			d[i] = (sum - x[i] * (dot[i] ^ 1) + P) % P;
		memset(y, 0, sizeof(y));
		memset(z, 0, sizeof(z));
		if (P == 2) {
			for (int i = 1; i <= m; i++) {
				for (int j = 1; j <= n; j++)
					y[0][i] += x[j] * a[j][i];
				y[0][i] %= P;
			}
			for (int i = 1; i <= n; i++) {
				for (int j = 1; j <= m; j++)
					z[i] += y[0][j] * b[j][i];
				z[i] %= P;
			}
		} else {
			for (int i = 1; i <= m; i++)
			for (int j = 1; j <= m; j++) {
				for (int k = 1; k <= n; k++)
					y[i][j] += x[k] * a[k][i] * a[k][j];
				y[i][j] %= P;
			}
			for (int i = 1; i <= n; i++) {
				for (int j = 1; j <= m; j++)
				for (int k = 1; k <= m; k++)
					z[i] += y[j][k] * b[j][i] * b[k][i];
				z[i] %= P;
			}
		}
		for (int i = 1; i <= n; i++)
			if (z[i] != d[i]) {
				solve(i);
				return 0;
			}
	}
	printf("-1 -1\n");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值