[NOI2013]向量内积

本文通过通俗解释,探讨了如何利用随机化方法解决涉及向量点积的线性代数问题,如找到矩阵中特定元素为0的位置。通过矩阵乘法和模2运算,逐步解析了k=2和k=3的解题思路,并揭示了随机选择集合的重要性。

题目

传送门 to LOJ

思路

这题如果直接从线性代数的角度去理解,还真的挺困难的。然后越思考越觉得有更通俗的理解。不如从 k = 2 k=2 k=2 开始做。

首先,我们需要想到这样一个东西:多个向量与多个向量,两两求点积,这实际上是 矩阵乘法,行向量乘列向量。

那么,将所有行向量拼成一个矩阵 A A A,我们只需要求出 A A T AA^T AAT 中为 0 0 0 的位置。不,不应该是求出,而是找到任意一个。显然这个运算是在模 2 2 2 意义下进行的。

那么我们想起这道题,试着搬到这边。啥情况无解呢?就是矩阵 B B B,满足 B i , i = x i ⋅ x i B_{i,i}={\bf x}_i\cdot{\bf x}_i Bi,i=xixi,其余位置均为 1 1 1 嘛。

随机一个列向量 C C C,来判断 A ( A T C ) A(A^TC) A(ATC) 是不是等于 B B B 。如果不等于 B B B,哪里是不同的位置呢?假如是 B i , j B_{i,j} Bi,j 不匹配,乘一个 C j C_{j} Cj 就贡献到了第 i i i 位。即,最终得到的列向量是第 i i i 位不同,那必定是第 i i i 行的某个值为 0 0 0,那么 x i {\bf x}_i xi 就可以被锁定,枚举另一个即可。

虽然 B B B 矩阵是 n × n n\times n n×n 的,无法求出,但是因为它很特殊, B × C B\times C B×C 可以直接算。时间复杂度 O ( n d ) \mathcal O(nd) O(nd)

然后 k = 3 k=3 k=3 又咋整呢?好像无法唯一确定 B B B 了……但是 1 2 ≡ 2 2 ≡ 1 ( m o d 3 ) 1^2\equiv 2^2\equiv 1\pmod{3} 12221(mod3),所以我们可以考虑把它平方。怎么把 B i , j B_{i,j} Bi,j 平方呢?竟然还得自己再写式子,差评!

最终的结果的第 x x x 位是
∑ i C i ⋅ ( ∑ j A x , j A j , i T ) 2 = ∑ j 1 , j 2 A x , j 1 A x , j 2 ∑ i C i A j 1 , i T A j 2 , i T \sum_{i}C_i\cdot \left(\sum_{j}A_{x,j}A^T_{j,i}\right)^2\\ =\sum_{j_1,j_2}A_{x,j_1}A_{x,j_2}\sum_{i}C_iA^T_{j_1,i}A^T_{j_2,i} iCi(jAx,jAj,iT)2=j1,j2Ax,j1Ax,j2iCiAj1,iTAj2,iT

于是我们要预处理后面那个求和式,记为 f ( j 1 , j 2 ) f(j_1,j_2) f(j1,j2),然后每个 x x x 暴力算就行。时间复杂度 O ( n d 2 ) \mathcal O(nd^2) O(nd2)

这时候,我们回头望月,感觉这东西似乎有别的理解。 a ⋅ b + a ⋅ c = a ⋅ ( b + c ) \bf a\cdot b+a\cdot c=a\cdot(b+c) ab+ac=a(b+c),这是点积的分配率。那么如果 a \bf a a r r r 个向量的点积都是 1 1 1,那么 a \bf a a 和这 r r r 个向量的和的点积应该是 r   m o d   2 r\bmod 2 rmod2 才对。我们随机一个列向量,本质就是随机一个集合,然后求出这个集合内的向量之和,然后与每个其他向量求点积,看是否符合标准。

所以我们必须多随机几次——如果恰好有偶数个 0 0 0,还是不能检测出错误。当然我们顺便求出了正确的概率:选择偶数个 0 0 0 和选择奇数个 0 0 0 的方案数相同,如果每次在所有方案中随机,正确率应该是 50 % 50\% 50% 。当然这是针对某一个向量而言的,多个向量同时作用的话,概率只会更高。

再看看 k = 3 k=3 k=3 的情况呢?不是简单的分配率了。但其实也简单,点积的平方嘛,就是在两个维度上都对应相乘,即 a i a j b i b j {\bf a}_i{\bf a}_j{\bf b}_i{\bf b}_j aiajbibj,仿照一次方时要记录 i i i,这里我们记录 i , j i,j i,j 。同样的,每个向量要分配一个系数,然后求和,再跟每个向量求 “点积”,看看是否能够匹配全 1 1 1 的值。

这里的 C i C_i Ci 可以在 { 0 , 1 , 2 } \{0,1,2\} {0,1,2} 中取值,可能稍微好点?这个概率比较难算。但就算仍然用 { 0 , 1 } \{0,1\} {0,1},概率也还是上面的 50 % 50\% 50%,靠谱。

代码

这个代码其实有一个小问题:当 n n n 很小时,比如 n = 3 n=3 n=3 时,由于用的是 r a n d o m    s h u f f l e \rm random\;shuffle randomshuffle 而不是重新 r a n d \rm rand rand,会难以检测出问题。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

int Mod, **vec, n, d;
int C[100000]; // random value
int tmp1[100], tmp2[100][100];
int self[100000]; // x_i.dot(x_i)
int check(){
	int sum = 0;
	rep(i,0,n-1) sum += C[i];
	if(Mod == 2){
		memset(tmp1,0,d<<2);
		rep(i,0,n-1) if(C[i]) rep(j,0,d-1)
			tmp1[j] ^= vec[i][j];
		for(int i=0,got; i<n; ++i){
			int want = sum+C[i]*(self[i]-1);
			rep(j,got=0,d-1)
				got ^= (tmp1[j]&vec[i][j]);
			if((want&1) != got) return i;
		}
	}
	else if(Mod == 3){
		rep(i,0,d-1) memset(tmp2[i],0,d<<2);
		rep(i,0,n-1) if(C[i])
			rep(j,0,d-1) if(vec[i][j])
				rep(k,0,d-1) // two dimensions
					tmp2[j][k] += vec[i][j]
						*vec[i][k]*C[i];
		rep(j,0,d-1) rep(k,0,d-1)
			tmp2[j][k] %= 3; // %= Mod
		for(int i=0,got; i<n; ++i){
			int want = sum+C[i]*(
				self[i]*self[i]-1);
			want = (want%3+3)%3;
			rep(j,got=0,d-1) rep(k,0,d-1)
				got += tmp2[j][k]*
					vec[i][j]*vec[i][k];
			if(want != got%3) return i;
		}
	}
	return -1;
}

int main(){
	n = readint(), d = readint();
	Mod = readint();
	vec = new int*[n];
	rep(i,0,n-1){
		vec[i] = new int[d];
		rep(j,0,d-1){
			vec[i][j] = readint()%Mod;
			self[i] += vec[i][j]*vec[i][j];
		}
		self[i] %= Mod;
	}
	int id = -1, sy = 10;
	for(; !(~id)&&sy; --sy){
		rep(i,0,n-1) C[i] = rand()&1;
		id = check();
	}
	if(id == -1) puts("-1 -1");
	else{
		for(int i=0,got; i<n; ++i){
			if(i == id) continue;
			rep(j,got=0,d-1)
				got += vec[id][j]*vec[i][j];
			if(got%Mod == 0){
				writeint(min(id,i)+1);
				putchar(' ');
				writeint(max(id,i)+1);
				break; // done
			}
		}
		putchar('\n');
	}
	return 0;
}

后记

我原本想的就是随机一个集合。比较类似 P o l l a r d − R h o \rm Pollard-Rho PollardRho 里面,将多个数乘在一起再求 gcd ⁡ \gcd gcd

总结一下,随机化无非就是:找必要条件,然后让它尽可能充分,方法是随机化;或者寻找某个频率较大的特征值(一般是 50 % 50\% 50%,例如此题),利用随机化和 c h e c k \rm check check

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值