[NOI2013] 向量内积
一、题目
二、解法
发现本题 \(k\) 只有 \(2,3\) 两种取值,我们先考虑 \(k=2\) 的时候怎么做。
可以固定 \(x_i\),去找有没有 \(x_j\) 合法。但是题目是不接受 \(O(n^2d)\) 判断的,可以考虑一些必要的判据,比如我们对 \(j\in[1,i)\) 求出一个前缀和向量,然后和 \(x_i\) 点积。考虑如果全 \(1\) 的话点积的结果是 \((i-1)\% 2\),那么如果不满足这个条件说明一定有一对 \((i,j)\) 满足 \(x_i,x_j\) 的点积为 \(0\)
这个判据是不充分的,所以我们把原序列 \(\tt random\_shuffle\) 一下可以提高正确率。
考虑 \(k=3\) 的时候怎么做,还是要搞出一个对于前缀而言的必要判据,但是因为此时出现了 \(2\),我们无法通过不全是某个数来推出存在某个数。考虑到 \(2^2\%3=1\),那么我们维护点积平方的前缀和,就可以类似 \(k=2\) 的方法做了。
维护点积平方前缀和的方法就是考虑把平方拆开:
\[(\sum a_ib_i)^2=\sum_{i}\sum_{j}a_ib_i\cdot a_jb_j=\sum_i\sum_j(a_ia_j)\cdot(b_ib_j)
\]
那么我们维护 \(b_{i,j}\) 表示第 \(i\) 位和第 \(j\) 位相乘的前缀和就可以快速计算了,时间复杂度 \(O(nd^2)\)
三、总结
找到一组合法解可以先看某个范围内存不存在解,再去这个范围里找,至于判断某范围是否存在解可以用一些正确率较高的必要判据,再加之随机化就很难被卡掉。
所有优于存在,注意它们之间的转化,本题判断不全为 \(1\) 优于判断存在 \(0\)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 100005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,d,k,id[M],a[M][105],b[105][105];
int cal(int x,int y)
{
int r=0;
for(int i=1;i<=d;i++)
r+=a[x][i]*a[y][i];
return r;
}
int check(int x)
{
int r=0;
for(int i=1;i<=d;i++)
for(int j=1;j<=d;j++)
{
r+=b[i][j]*a[x][i]*a[x][j];
b[i][j]+=a[x][i]*a[x][j];
}
return r%k;
}
signed main()
{
n=read();d=read();k=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=d;j++)
a[i][j]=read()%k;
for(int t=1;t<=10;t++)
{
memset(b,0,sizeof b);
for(int i=1;i<=n;i++) id[i]=i;
random_shuffle(id+1,id+1+n);
for(int i=1;i<=n;i++)
if(check(id[i])!=(i-1)%k)
{
for(int j=1;j<i;j++)
if(cal(id[i],id[j])%k==0)
{
if(id[i]>id[j]) swap(i,j);
printf("%d %d\n",id[i],id[j]);
return 0;
}
}
}
puts("-1 -1");
}