传送门
思路
唉,我太弱了,什么都不会,听都没有听说过这类题,唉,我太弱啦!
显然这道题可以在 的时间复杂度内得出答案,顺利得到 分。注意到, 是不会影响 的算法的,而数据规模中 只会等于 或者 ,说明最后的算法一定跟 有关。另外,显然的是 的范围没有任何用处,我们只需要在模 意义下进行计算就可以了。
一个 Naive 但又很显然的想法是,既然我们挨着检查算不完,那我们干脆每次随机两个向量相乘。但显然的是,这样做的概率是无法得到保证的,成功得到 分。
我们可以用矩阵来表示一堆向量两两相乘的结果。我们设矩阵 表示一堆向量,则 的元素 表示的就是第 个向量与第 个向量的点积。那么我们可以这样判断是否有解:如果 的非对角线元素存在 (从这里开始以及下面的讨论中的运算都是在模 意义下进行的),则一定有解,且它的行数和列数就是向量的编号。
假设我们有一台运算能力和存储空间无限的计算机,我们考虑上面的操作如何进行。首先我们考虑
的情况。显然,我们可以保存
和
,然后在
的时间复杂度内计算出它们的积。接下来我们要找非对角线上的元素是否存在
,我们可以直接用一个二重循环,也可以考虑这样一个方法:构造矩阵
,满足:
我们检查 是否等于 ,如果等于,说明无解,否则说明有解。
由于我们的计算机运算能力有限,所以我们显然不能暴力开数组,暴力算矩阵乘法。我们考虑这样做:设一个长度为 的随机的 01 列向量 ,若 ,那么我们可以断定 ,且我们知道是哪一行不一样。右式显然可以在 的时间复杂度内计算出来。左式由于等于 ,也能够在 的时间复杂度内计算出来。在找到哪一行不一样后,我们可以在 的时间复杂度内找出另外一个向量,问题就解决了。
然而,若 ,我们却没法断定 ,因为这个过程实际上对信息进行了有损压缩。考虑 的实际意义,它实际上是在考虑 和 均为 的个数。但是需要注意的是,这个运算必须在模意义下进行,因此个数就变成了个数的奇偶性。那么显然,若一次测试后报告无解,那么实际上无解的几率为 1,所以在进行足够多次后仍然报告无解的话,我们就可以认为无解。
我们考虑 时该怎么做。当 时,我们没有办法构造 矩阵了,因为非对角线元素有可能为 ,也有可能为 ,我们无法知道到底该是什么。
注意到, , , ,也就是说,我们将向量点积的结果平方,就能将问题转换成判断 矩阵是否相等了。
考虑把结果平方后,原式是如何变化的:
我们把 看作一个整体,则相当于变成了 维的向量相乘,模数变成了 (注意:随机生成的列向量仍然是 01 向量,但是模数变成了 )。如法炮制即可。
这里的转换实际上是这个意思:运算仍然在模 意义下进行,但是我们用一种特殊的手段强行让最后的结果在模 意义下只存在 和 ,且 对应原来的零, 对应原来的非零。
参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
#define loop register int
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef int INT_PUT;
INT_PUT readIn()
{
INT_PUT a = 0; bool positive = true;
char ch = getchar();
while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
if (ch == '-') { positive = false; ch = getchar(); }
while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20]; int length = 0;
if (x < 0) putchar('-'); else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
}
const int maxn = int(1e5) + 5;
int n, d, K;
struct Rect
{
int c[3000105];
int* operator[](int x)
{
return c + x * d;
}
const int* operator[](int x) const
{
return c + x * d;
}
} vec;
#define RunInstance(x) delete new x
struct brute
{
brute()
{
for (int i = 0; i < n; i++)
for (int j = 0; j < d; j++)
vec[i][j] %= K;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
{
int sum = 0;
for (int k = 0; k < d; k++)
sum += vec[i][k] * vec[j][k];
if (!(sum % K))
{
printOut(i + 1);
putchar(' ');
printOut(j + 1);
return;
}
}
printf("-1 -1");
}
};
struct work2
{
int random[maxn];
int diagonal[maxn];
int temp[105];
work2()
{
for (loop i = 0; i < n; i++)
{
loop t = 0;
for (loop j = 0; j < d; j++)
t += vec[i][j] * vec[i][j];
t &= 1;
diagonal[i] = t;
}
int T = 1e8 / (n * d);
while (T--)
{
int count1 = 0;
for (loop i = 0; i < n; i++)
count1 += random[i] = rand() & 1;
for (loop j = 0; j < d; j++)
{
loop t = 0;
for (loop i = 0; i < n; i++)
t += vec[i][j] & random[i];
temp[j] = t;
}
for (loop i = 0; i < n; i++)
{
loop t = 0;
for (loop j = 0; j < d; j++)
t += vec[i][j] * temp[j];
if ((t & 1) != ((count1 - (!diagonal[i] && random[i])) & 1))
{
for (loop j = 0; j < n; j++) if (i != j)
{
loop sum = 0;
for (loop k = 0; k < d; k++)
sum += vec[i][k] * vec[j][k];
if (!(sum % K))
{
if (i > j)
std::swap(i, j);
printOut(i + 1);
putchar(' ');
printOut(j + 1);
return;
}
}
}
}
}
puts("-1 -1");
}
};
struct work3
{
int random[maxn];
int diagonal[maxn];
int temp[105 * 105]; // note: 数组要开足够大
work3()
{
for (loop i = 0; i < n; i++)
{
loop t = 0;
loop v;
for (loop j = 0; j < d; j++)
for (loop k = 0; k < d; k++)
t += (v = vec[i][j] * vec[i][k]) * v;
t %= 3; // note: 注意在模 3 意义下进行
diagonal[i] = t;
}
int T = 1e8 / (n * d * d);
while (T--)
{
int count1 = 0;
for (loop i = 0; i < n; i++)
count1 += random[i] = rand() & 1; // note: 生成的仍是 01 向量
for (loop j = 0, v = 0; j < d; j++)
for (loop k = 0; k < d; k++, v++)
{
loop t = 0;
for (loop i = 0; i < n; i++)
t += vec[i][j] * vec[i][k] * random[i];
temp[v] = t % 3;
}
for (loop i = 0; i < n; i++)
{
loop t = 0;
for (loop j = 0, v = 0; j < d; j++)
for (loop k = 0; k < d; k++, v++)
t += vec[i][j] * vec[i][k] * temp[v] % 3;
if ((t % 3) != ((count1 - (!diagonal[i] && random[i])) % 3)) // note: 注意在模 3 意义下进行
{
for (loop j = 0; j < n; j++) if (i != j)
{
loop sum = 0;
for (loop k = 0; k < d; k++)
sum += vec[i][k] * vec[j][k];
if (!(sum % K))
{
if (i > j)
std::swap(i, j);
printOut(i + 1);
putchar(' ');
printOut(j + 1);
return;
}
}
}
}
}
puts("-1 -1");
}
};
void run()
{
n = readIn();
d = readIn();
K = readIn();
for (int i = 0; i < n; i++)
for (int j = 0; j < d; j++)
vec[i][j] = readIn();
if ((LL)n * n * d <= LL(3e8))
RunInstance(brute);
else if (K == 2)
RunInstance(work2);
else
RunInstance(work3);
}
int main()
{
run();
return 0;
}
- 事实上我并没有成功证明,这里口胡了一下。 ↩