2022牛客暑期多校第二场 I. let fat tension
2022牛客暑期多校第二场 I. let fat tension
题意
给定 \(n,k,d\),\(n\) 为 \(X\) 和 \(Y\) 向量的个数,\(k\) 为 \(X\) 向量的维数,\(d\) 为 \(Y\) 向量的维数。
定义 \(le(i,j) = \frac{X_i\cdot X_j}{|X_i|\cdot|X_j|}\) (实际上就是夹角余弦)
定义 \(Y_i^{new} = \sum_{j=1}^n le(i,j)Y_i\)。
计算 \(Y_i^{new}\) 向量。输出 \(i = 1,2,\dots,n\) 的答案。
分析
对于 \(X_i\) 向量两两之间求夹角余弦可以先将 \(X_i\) 向量单位化。
单位化之后,构造一个 \(n \times k\) 的矩阵 \(X\),矩阵的第 \(i\) 行塞上单位化后的 \(X_i\) 向量。
计算 \(XX^T\) 矩阵,其中第 \(i\) 行,第 \(j\) 列会得到向量 \(X_i\) 与 \(X_j\) 的夹角余弦。
再构造一个 \(n \times d\) 的矩阵 \(Y\),矩阵的第 \(i\) 行塞上 \(Y_i\) 向量。
此时根据题意,计算出矩阵 \(XX^TY\),其第 \(i\) 行就是要计算的 \(Y_i^{new}\) 向量。
计算这 \(3\) 个矩阵的乘积如果从左向右依次计算将会是 \(O(n^2k+n^2d)\) 的,然而,如果先算后面两个矩阵乘积的结果,再算第一个与后面两个矩阵乘积结果的乘积,复杂度将会变成 \(O(nkd)\)。
代码
#include <cmath>
#include <iomanip>
#include <iostream>
using namespace std;
const int maxn = 1e4 + 10;
const int maxk = 60;
const int maxd = 60;
int n, k, d;
double x[maxn][maxk], y[maxn][maxd], tmp[maxk][maxd], ans[maxn][maxd];
int main() {
cin >> n >> k >> d;
for (int i = 0; i < n; i++) {
double norm = 0;
for (int j = 0; j < k; j++) {
cin >> x[i][j];
norm += x[i][j] * x[i][j];
}
for (int j = 0; j < k; j++) {
x[i][j] /= sqrt(norm);
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < d; j++) {
cin >> y[i][j];
}
}
for (int p = 0; p < k; p++) {
for (int q = 0; q < n; q++) {
for (int r = 0; r < d; r++) {
tmp[p][r] += x[q][p] * y[q][r];
}
}
}
for (int p = 0; p < n; p++) {
for (int q = 0; q < k; q++) {
for (int r = 0; r < d; r++) {
ans[p][r] += x[p][q] * tmp[q][r];
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < d; j++) {
cout << fixed << setprecision(8) << ans[i][j] << ' ';
}
cout << '\n';
}
return 0;
}