1. 程式人生 > 其它 >[NOI 2013] 向量內積

[NOI 2013] 向量內積

極度憤怒。 目錄

題目

傳送門

解法

觀察資料,我們發現 \(k\) 只有 \(2,3\) 兩種情況,這提醒我們分類討論。

\(\text{case}_1:k=2\)

不妨將兩兩向量內積化成一個矩陣:設 \(n\)\(d\) 維行向量組成的矩陣為 \(A\),那麼這個矩陣為 \(Y=AA^T\)

對於 \(k=2\),我們只需判斷 \(Y\) 是否等於 \(E\)\(E\) 是全 \(1\) 矩陣)。

用類似 這道題 的方法來判斷即可。在判斷時,可以用 \(\mathcal O(nd)\) 內找出哪個行向量 \(pos\)

\(0\)。然後我們再列舉其它向量,暴力計算它與 \(pos\) 的內積即可。

總時間複雜度 \(\mathcal O(nd)\)

\(\text{case}_2:k=3\)

因為 \(Y\) 最終可能有 \(0,1,2\) 三種值,上面的方法就行不通了。

然後有個很妙的轉化:設 \(Z_{i,j}=Y_{i,j}^2\)。這樣就又變成了 \(0,1\) 兩種值。

問題在於,\(Z\)\(Y\) 單項的平方,並不能直接矩乘獲得,所以嘗試推一推式子(\(\alpha\) 是隨機的向量):

\[(Z\alpha)_i=\sum_{j=1}^nZ_{i,j}\alpha_j \]\[=\sum_{j=1}^nY_{i,j}^2\alpha_j \]\[=\sum_{j=1}^n\alpha_j\left(\sum_{k=1}^d A_{i,k}A^T_{k,j} \right)^2 \]

這時可以做到 \(\mathcal O(n^2d)\)

的複雜度,但它和暴力複雜度是一樣的。可以嘗試拋開與 \(i\) 無關的項然後進行預處理。

\[=\sum_{j=1}^n\alpha_j\left(\sum_{k_1=1}^d A_{i,k_1}A_{j,k_1} \right)\left(\sum_{k_2=1}^d A_{i,k_2}A_{j,k_2} \right) \]\[=\sum_{k_1=1}^d \sum_{k_2=1}^d A_{i,k_1}A_{i,k_2}\sum_{j=1}^n \alpha_jA_{j,k_1}A_{j,k_2} \]

後面一坨可以 \(\mathcal O(nd^2)\) 預處理成函式 \(g(k_1,k_2)\)

。因為還要列舉 \(i\),來判斷 \((Z\alpha)_i\)\((E\alpha)_i\) 是否相等,也是 \(\mathcal O(nd^2)\) 的。

我隨機了 \(5\) 次,類似 這道題 的方法,得出錯誤概率為 \(\frac{1}{2^5}\)(感覺好奇怪)。

程式碼

#include <cstdio>

#define print(x,y) write(x),putchar(y) 

template <class T> inline T read(const T sample) {
    T x=0; int f=1; char s;
    while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
    while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
    return x*f;
}
template <class T> inline void write(const T x) {
    if(x<0) return (void) (putchar('-'),write(-x));
    if(x>9) write(x/10);
    putchar(x%10^48);
}

#include <cstdlib>
#include <iostream>
using namespace std;

const int maxn=1e5+5,maxd=105;

int n,d,k,v[maxn][maxd],sum;
int a[maxd],r[maxn],pos;
int g[maxd][maxd];
bool flag;

void calc_2() {
	for(int i=1;i<=d;++i) a[i]=0;
	for(int i=1;i<=d;++i)
		for(int j=1;j<=n;++j)
			a[i]=(a[i]+v[j][i]*r[j])%k;
	for(int i=1;i<=n;++i) {
		int tmp=0;
		for(int j=1;j<=d;++j)
			tmp=(tmp+a[j]*v[i][j])%k;
		if(tmp!=sum) {
			flag=1,pos=i;
			break;
		}
	}
}

void calc_3() {
	for(int k1=1;k1<=d;++k1)
		for(int k2=1;k2<=d;++k2) {
			g[k1][k2]=0;
			for(int j=1;j<=n;++j)
				g[k1][k2]=(g[k1][k2]+
							v[j][k1]*v[j][k2]*r[j])%k;
		}
	for(int i=1;i<=n;++i) {
		int tmp=0;
		for(int k1=1;k1<=d;++k1)
			for(int k2=1;k2<=d;++k2)
				tmp+=v[i][k1]*v[i][k2]*g[k1][k2];
		if(tmp%k!=sum) {
			flag=1,pos=i;
			break;
		}
	}
}

int main() {
	srand(12242013);
	n=read(9),d=read(9),k=read(9);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=d;++j)
			v[i][j]=read(9)%k;
	for(int T=1;T<=5;++T) {
		sum=0;
		for(int i=1;i<=n;++i)
			r[i]=rand()%k,sum=(sum+r[i])%k;
		if(k==2) calc_2();
		else calc_3();
		if(flag) break;
	}
	if(!flag) puts("-1 -1");
	else {
		for(int i=1;i<=n;++i)
			if(i^pos) {
				int tmp=0;
				for(int j=1;j<=d;++j)
					tmp+=v[pos][j]*v[i][j];
				if(tmp%k==0) {
					printf("%d %d\n",min(pos,i),max(pos,i));
					break;
				}
			}
	}
	return 0;
}