Luogu 1224 [NOI 2013] 向量内积

传送门
思路

唉,我太弱了,什么都不会,听都没有听说过这类题,唉,我太弱啦!

显然这道题可以在 O ( n 2 d ) 的时间复杂度内得出答案,顺利得到 60 分。注意到, k 是不会影响 O ( n 2 d ) 的算法的,而数据规模中 k 只会等于 2 或者 3 说明最后的算法一定跟 k 有关。另外,显然的是 x 的范围没有任何用处,我们只需要在模 k 意义下进行计算就可以了。

一个 Naive 但又很显然的想法是,既然我们挨着检查算不完,那我们干脆每次随机两个向量相乘。但显然的是,这样做的概率是无法得到保证的,成功得到 0 分。

我们可以用矩阵来表示一堆向量两两相乘的结果。我们设矩阵 A 表示一堆向量,则 A × A T 的元素 a i , j 表示的就是第 i 个向量与第 j 个向量的点积。那么我们可以这样判断是否有解:如果 A × A T 的非对角线元素存在 0 (从这里开始以及下面的讨论中的运算都是在模 k 意义下进行的),则一定有解,且它的行数和列数就是向量的编号。

假设我们有一台运算能力和存储空间无限的计算机,我们考虑上面的操作如何进行。首先我们考虑 k = 2 的情况。显然,我们可以保存 A A T ,然后在 O ( n 2 d ) 的时间复杂度内计算出它们的积。接下来我们要找非对角线上的元素是否存在 0 ,我们可以直接用一个二重循环,也可以考虑这样一个方法:构造矩阵 D ,满足:

d i , j = { 1 i j a i a i i = j

我们检查 A × A T 是否等于 D ,如果等于,说明无解,否则说明有解。

由于我们的计算机运算能力有限,所以我们显然不能暴力开数组,暴力算矩阵乘法。我们考虑这样做:设一个长度为 n 的随机的 01 列向量 R ,若 A × A T × R D × R ,那么我们可以断定 A × A T D ,且我们知道是哪一行不一样。右式显然可以在 O ( n d ) 的时间复杂度内计算出来。左式由于等于 A × ( A T × R ) ,也能够在 O ( n d ) 的时间复杂度内计算出来。在找到哪一行不一样后,我们可以在 O ( n d ) 的时间复杂度内找出另外一个向量,问题就解决了。

然而,若 A × A T × R = D × R ,我们却没法断定 A × A T = D ,因为这个过程实际上对信息进行了有损压缩。考虑 D × R 的实际意义,它实际上是在考虑 D R 均为 1 的个数。但是需要注意的是,这个运算必须在模意义下进行,因此个数就变成了个数的奇偶性。那么显然,若一次测试后报告无解,那么实际上无解的几率为 1 2 1,所以在进行足够多次后仍然报告无解的话,我们就可以认为无解。


我们考虑 k = 3 时该怎么做。当 k = 3 时,我们没有办法构造 D 矩阵了,因为非对角线元素有可能为 1 ,也有可能为 2 ,我们无法知道到底该是什么。

注意到, 0 2 0 ( mod 3 ) 1 2 1 ( mod 3 ) 2 2 1 ( mod 3 ) ,也就是说,我们将向量点积的结果平方,就能将问题转换成判断 01 矩阵是否相等了。

考虑把结果平方后,原式是如何变化的:

( i = 1 d a i b i ) ( i = 1 d a i b i ) = i = 1 d j = 1 d a i a j b i b j

我们把 a i a j 看作一个整体,则相当于变成了 d 2 维的向量相乘,模数变成了 3 (注意:随机生成的列向量仍然是 01 向量,但是模数变成了 3 )。如法炮制即可。

这里的转换实际上是这个意思:运算仍然在模 3 意义下进行,但是我们用一种特殊的手段强行让最后的结果在模 3 意义下只存在 0 1 ,且 0 对应原来的零, 1 对应原来的非零。

参考代码
#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;
}

  1. 事实上我并没有成功证明,这里口胡了一下。

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/80436130