1 条题解
-
0
「NOI2013」向量内积 题解
题目大意
给定 个 维向量 ,每个分量为非负整数。我们需要判断是否存在两个向量 和 (),使得它们的内积 是 的倍数。
关键约束
- ,,但 只有 或
- 直接暴力计算所有 的内积会超时
- 需要巧妙的方法利用 很小这个性质
核心思想
1. 问题转化
定义矩阵 ,其中第 行是向量 。
我们想检查是否存在 使得:
这等价于检查 ( 矩阵)的非对角线元素是否有 。
2. 随机化检验
直接计算 是 ,太大。
关键技巧:随机取一个向量 ,计算:
首先计算 ( 维向量),然后计算 ( 维向量)。
如果存在 使得 ,那么对于随机的 ,有较高的概率能检测到异常。
3. 具体算法
当 时:
所有运算在 (模2域)中进行。
算法步骤:
- 随机生成一个 维0-1向量
- 计算 ( 维向量)
- 计算 ( 维向量)
- 计算 (对角线元素和)
- 对于每个 ,如果 ,那么 可能与某个 满足条件
- 验证这些 是否真的存在配对
当 时:
需要处理模3运算。因为3是质数, 是域。
技巧:对于模3,有 (因为 , , )。
算法实现细节
的优化
因为运算在模2下,可以用bitset加速:
- 向量用bitset表示
- 矩阵乘法用bitset优化
随机化次数
为了达到高正确率,需要多次随机(如10-20次)。
错误概率分析
根据Schwartz-Zippel引理或类似原理,单次检测失败的概率约为 ,因此多次检测可大幅降低失败概率。
代码框架
#include <iostream> #include <vector> #include <algorithm> #include <random> #include <bitset> using namespace std; int n, d, k; // k=2的情况 void solve_k2() { // 读入向量 vector<bitset<100>> vecs(n); // 假设d≤100 for (int i = 0; i < n; i++) { for (int j = 0; j < d; j++) { int x; cin >> x; if (x & 1) vecs[i].set(j); } } // 随机检测多次 mt19937 rng(random_device{}()); uniform_int_distribution<int> dist(0, 1); for (int iter = 0; iter < 20; iter++) { // 生成随机向量r vector<int> r(n); bitset<100> r_bits; // 用于快速计算 for (int i = 0; i < n; i++) { r[i] = dist(rng); } // 计算 v = A * r mod 2 bitset<100> v; for (int i = 0; i < n; i++) { if (r[i]) { v ^= vecs[i]; } } // 计算 C = A^T * v mod 2 vector<int> C(n, 0); for (int i = 0; i < n; i++) { C[i] = (vecs[i] & v).count() & 1; } // 计算对角线贡献 int s = 0; for (int i = 0; i < n; i++) { if (r[i]) { // x_i^T x_i mod 2 int diag = vecs[i].count() & 1; s ^= diag; } } // 检查异常 for (int i = 0; i < n; i++) { int expected = s; if (r[i]) { int diag = vecs[i].count() & 1; expected ^= diag; } if (C[i] != expected) { // i可能与某个j配对 for (int j = 0; j < n; j++) { if (i == j) continue; // 计算内积 int dot = (vecs[i] & vecs[j]).count() & 1; if (dot == 0) { if (i < j) cout << i+1 << " " << j+1 << endl; else cout << j+1 << " " << i+1 << endl; return; } } } } } cout << "-1 -1" << endl; } // k=3的情况 void solve_k3() { vector<vector<int>> vecs(n, vector<int>(d)); for (int i = 0; i < n; i++) { for (int j = 0; j < d; j++) { cin >> vecs[i][j]; vecs[i][j] %= 3; } } mt19937 rng(random_device{}()); uniform_int_distribution<int> dist(0, 2); for (int iter = 0; iter < 20; iter++) { vector<int> r(n); for (int i = 0; i < n; i++) { r[i] = dist(rng); } // 计算 v = A * r mod 3 vector<int> v(d, 0); for (int i = 0; i < n; i++) { if (r[i]) { for (int j = 0; j < d; j++) { v[j] = (v[j] + r[i] * vecs[i][j]) % 3; } } } // 计算 C = A^T * v mod 3 vector<int> C(n, 0); for (int i = 0; i < n; i++) { for (int j = 0; j < d; j++) { C[i] = (C[i] + vecs[i][j] * v[j]) % 3; } } // 计算对角线贡献 int s = 0; for (int i = 0; i < n; i++) { if (r[i]) { // x_i^T x_i mod 3 int diag = 0; for (int j = 0; j < d; j++) { diag = (diag + vecs[i][j] * vecs[i][j]) % 3; } s = (s + r[i] * diag) % 3; } } // 检查异常 for (int i = 0; i < n; i++) { int expected = s; if (r[i]) { int diag = 0; for (int j = 0; j < d; j++) { diag = (diag + vecs[i][j] * vecs[i][j]) % 3; } expected = (expected - r[i] * diag + 9) % 3; } if (C[i] != expected) { // i可能与某个j配对 for (int j = 0; j < n; j++) { if (i == j) continue; int dot = 0; for (int t = 0; t < d; t++) { dot = (dot + vecs[i][t] * vecs[j][t]) % 3; } if (dot == 0) { if (i < j) cout << i+1 << " " << j+1 << endl; else cout << j+1 << " " << i+1 << endl; return; } } } } } cout << "-1 -1" << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n >> d >> k; if (k == 2) { solve_k2(); } else { // k == 3 solve_k3(); } return 0; }样例解析
输入:
3 5 2 1 0 1 0 1 1 1 0 1 0 0 1 0 1 1计算:
- $(x_1, x_2) = 1 \times 1 + 0 \times 1 + 1 \times 0 + 0 \times 1 + 1 \times 0 = 1 \not\equiv 0 \mod 2$
- $(x_1, x_3) = 1 \times 0 + 0 \times 1 + 1 \times 0 + 0 \times 1 + 1 \times 1 = 1 \not\equiv 0 \mod 2$
- $(x_2, x_3) = 1 \times 0 + 1 \times 1 + 0 \times 0 + 1 \times 1 + 0 \times 1 = 2 \equiv 0 \mod 2$
输出:
2 3复杂度分析
- 每次随机检测:
- 检测次数:常数次(如20次)
- 总复杂度:,可接受
总结
本题的巧妙之处在于:
- 利用很小的性质进行模运算优化
- 使用随机化方法避免计算所有对内积
- 通过矩阵乘法的结合律来加速
这种"随机化+线性代数"的思路在竞赛中很经典,需要理解其背后的数学原理和概率保证。
- 1
信息
- ID
- 6128
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者