#121. 【NOI2013】向量内积
两个 dd 维向量 A=[a1,a2,…,ad]A=[a1,a2,…,ad] 与 B=[b1,b2,…,bd]B=[b1,b2,…,bd] 的内积为其相对应维度的权值的乘积和,即:
现在有 nn 个 dd 维向量 x1,x2,…,xnx1,x2,…,xn,小喵喵想知道是否存在两个向量的内积为 kk 的倍数。请帮助她解决这个问题。
输入格式
第一行包含 33 个正整数 n,d,kn,d,k,分别表示向量的个数,维数以及待检测的倍数。
接下来 nn 行每行有 dd 个非负整数,其中第 ii 行的第 jj 个整数表示向量 xixi 的第 jj 维权值 xi,jxi,j。
输出格式
包含两个整数,用空格隔开。
如果存在两个向量 xp,xqxp,xq 的内积为 kk 的整数倍,则输出两个向量的编号 pp 与 qq (要求 p<qp<q)。如果存在多组这样的向量组合,输出其中任意一组即可。
若不存在这样的向量组合,则输出两个 −1−1。
样例一
input
3 5 2 1 0 1 0 1 1 1 0 1 0 0 1 0 1 1
output
2 3
explanation
⟨x1,x2⟩=1⟨x1,x2⟩=1,⟨x1,x3⟩=1⟨x1,x3⟩=1,⟨x2,x3⟩=2⟨x2,x3⟩=2。
限制与约定
测试点编号 | nn | dd | kk | xi,jxi,j |
---|---|---|---|---|
1 | 22 | 2020 | 22 | ≤10≤10 |
2 | 55 | 2020 | 22 | ≤10≤10 |
3 | 1010 | 2020 | 33 | ≤10≤10 |
4 | 2020 | 2020 | 22 | ≤100≤100 |
5 | 5050 | 2020 | 33 | ≤100≤100 |
6 | 5050 | 5050 | 22 | ≤1000≤1000 |
7 | 5050 | 5050 | 33 | ≤3000000≤3000000 |
8 | 8080 | 8080 | 22 | ≤3000000≤3000000 |
9 | 100100 | 100100 | 33 | ≤3000000≤3000000 |
10 | 500500 | 100100 | 33 | ≤3000000≤3000000 |
11 | 10001000 | 100100 | 22 | ≤3000000≤3000000 |
12 | 10001000 | 100100 | 33 | ≤3000000≤3000000 |
13 | 1000010000 | 100100 | 22 | <10<10 |
14 | 1000010000 | 100100 | 33 | <10<10 |
15 | 1500015000 | 100100 | 22 | <10<10 |
16 | 1800018000 | 100100 | 22 | <10<10 |
17 | 2000020000 | 100100 | 22 | <10<10 |
18 | 5000050000 | 3030 | 33 | <10<10 |
19 | 8000080000 | 3030 | 33 | <10<10 |
20 | 100000100000 | 3030 | 33 | <10<10 |
时间限制:5s5s
空间限制:256MB256MB
下载
#include<cstdio> #define set(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout); #define EF if(ch==EOF) return x; using namespace std; typedef long long ll; const int N=1e5+5; int n,d,K,flag; ll ans,a[N][101]; inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;EF;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void work2(){ for(int i=1;i<=n;i++){ for(int j=1;j<i;j++){ ans=0; for(int k=0;k<d;k++){ ans+=a[i][k]*a[j][k]; } if(!(ans&1)){printf("%d %d\n",j,i);flag=1;return ;} } } } void work3(){ for(int i=1;i<=n;i++){ for(int j=1;j<i;j++){ ans=0; for(int k=0;k<d;k++){ ans+=a[i][k]*a[j][k]; } if(!(ans%K)){printf("%d %d\n",j,i);flag=1;return ;} } } } int main(){ set(meow); n=read();d=read();K=read(); for(int i=1;i<=n;i++){ for(int j=0;j<d;j++){ a[i][j]=read(); } } if(K==2) work2(); else work3(); if(!flag) puts("-1 -1"); return 0; }
——转自网络:
先考虑k=2的情况
以下运算全部在mod 2的环境下运行。
将n个向量压进一个[Math Processing Error]的矩阵A1里。
设一个[Math Processing Error]的矩阵A2满足[Math Processing Error]
设矩阵[Math Processing Error]
如果存在一对向量的内积为0
那么等价于矩阵P在非对角线位置上存在一个元素0。
对角线上的0元素干扰了我们的判断。
所以再设一个[Math Processing Error]的矩阵F
满足[Math Processing Error] 其他元素都为0。
由于只有对角线上的元素是非零的,所以可以用个数组存下F。
再设一个[Math Processing Error]的矩阵G,元素全部为1。
再设一个矩阵T=G-F-P
至此,存在一对向量的内积为0已经等价于矩阵T中存在一个元素1。
但同时,计算这些矩阵的时间、空间复杂度都是巨大的。
我们也无法判断矩阵T是否存在元素1,更无法判断他在哪里。
我们一步步来。
随机一个[Math Processing Error]的矩阵X
如果我们能求出矩阵L=[Math Processing Error],那么如果[Math Processing Error]
那么T矩阵的第i行上就一定存在 一个元素1
剩下的事情就很容易了。
但问题是T是一个[Math Processing Error]的矩阵,太巨大了,既存不下,也算不得,怎么办?
我们观察到: [Math Processing Error]
什么意思?分配律!
[Math Processing Error]
G是一个元素全部为1的[Math Processing Error]的矩阵,O(n)算出[Math Processing Error]
F只有对角线上的元素非0,O(n)算出[Math Processing Error]
而求[Math Processing Error]可以利用结合律
求[Math Processing Error]的复杂度是O(dn)
再求[Math Processing Error],复杂度也是O(dn)
完美的避开了高复杂度的运算。
由于矩阵[Math Processing Error]算出来没有1的概率是很小的,所以随机一定的次数大概就没问题了。
对于k=3的情况。 由于运算出来的结果除了0,1,还有2
所以上述方法就不能继续沿用了
但我们再进一步观察: [Math Processing Error]
也就是说,如果我们求的是内积平方,那么问题就解决了。
但问题是如果求内积平方就不能用矩阵来做了。
所以我们再观察一下所谓的“内积平方”。
假设d=3吧。
向量A=[a1,a2,a3],向量B=[b1,b2,b3]
貌似就变成了:
向量A[a1a1,a1a2,a1a3,a2a1,a2a2,a2a3,a3a1,a3a2,a3a3]
向量B[b1b1,b1b2,b1b3,b2b1,b2b2,b2b3,b3b1,b3b2,b3b3]
两者的内积。
也就是说只要把d维向量变成上述[Math Processing Error]维的向量
问题就和k=2的情况几乎完全相同了。
时间复杂度:
对于k=2:O(CND)
对于k=3:O(CND^2)
期望得分:100
#include<ctime> #include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; const int N=1e5+10; const int Z=110; int n,m,p,v[N][Z],r[N],b[Z],c[Z][Z]; struct buf{ char z[1<<25],*s; buf():s(z){ fread(z,1,1<<25,stdin); } operator int(){ int x=0,f=1; for(;*s<'0'||*s>'9';s++)if(*s=='-')f=-1; for(;*s>='0'&&*s<='9';s++)x=x*10+*s-'0'; return x*f; } }R; int sol(int i){ int s=0; if(p==2) for(int j=1;j<=m;j++) s^=b[j]&v[i][j],b[j]^=v[i][j]; else for(int j=1;j<=m;j++) for(int k=1;k<=m;k++) s+=c[j][k]*v[i][j]*v[i][k],c[j][k]+=v[i][j]*v[i][k]; return s%p; } void sol(int i,int j){ int t(0); for(int k=1;k<=m;k++) t+=v[i][k]*v[j][k]; if(!(t%p)){ if(i>j) swap(i,j); printf("%d %d\n",i,j); exit(0); } } int main(){ n=R;m=R;p=R; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) v[i][j]=R%p; for(int i=1;i<=n;i++) r[i]=i; srand(20000329); random_shuffle(r+1,r+n+1); for(int i=1;i<=n;i++) if(sol(r[i])!=(i-1)%p) for(int j=1;;sol(r[i],r[j++])); puts("-1 -1"); return 0; }