BZOJ3243/UOJ121 [Noi2013]向量内积
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。
本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!
Description
两个d 维向量A=[a1,a2,...,ad]与B=[b1,b2,...,bd]的内积为其相对应维度的权值的乘积和,即:
现有 n 个d 维向量x1,...,xn ,小喵喵想知道是否存在两个向量的内积为k的倍数。请帮助她解决这个问题
Input
Output
Sample Input
0 0 1 1 1 1 1 0 1 1 1 0 1 0 0 0 1 1 1 1
1 0 1 0 1 0 1 1 1 1 0 1 1 1 0 1 1 0 1 0
Sample Output
正解:随机化+矩阵乘法+搜索
解题报告:
这道题非常有意思呀…
首先如果把所有向量列在一起可以得到一个n*d的矩阵,而将这个矩阵转置得到一个转置矩阵,用矩阵乘转置矩阵,将得到的新矩阵。
容易发现,新矩阵的第i行第j个数就是第i个向量和第j个向量的内积。
如果在模2意义下,只要新矩阵中存在0,则说明存在是2的倍数的组合。
而我们可以和全1矩阵进行比较。如果不相等则说明存在,暴力寻找;否则不存在。
注意到为了支持快速判断两个大矩阵是否相等,我需要用一个另外的矩阵分别乘等式两边的矩阵,如果最终结果相同则视为两个矩阵相等。
这样做有可能出错,多随几次提高判断正确的概率。
对于k=3的情况,不能用上述做法做,考虑把结果平方一下,则可以把2化成1。考虑不用矩乘,直接用点积:
考虑我先随机一个1到n的排列,每次用当前排列所代表的向量,去与之前的所有向量做点积。得到的答案平方之后,再加起来。
那么我可以得到一个权值,如果为i-1则说明全为1,与全1矩阵相等。否则出现了0,暴力寻找,输出答案即可。
考虑如何优化快速求与之前所有向量的点积的平方和。
${(\sum_{i=1}^{d}a_i*b_i)^2}$这是a向量和b向量的点积的平方。
${(\sum_{i=1}^{d}a_i*b_i)^2}=\sum_{i=1}^{d}\sum_{j=1}^{d}a_i*b_i*a_j*b_j$
那么我令a为排列的第i个数所代表的向量,则令b为排列的前i-1个数的前缀和,则可快速求得。
//It is made by ljh2000 #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> #include <ctime> using namespace std; typedef long long LL; const int MAXN = 100011; const int MAXD = 102; int n,d,k,c[MAXN],ans[MAXN],c2[MAXN],q[MAXN]; int a[MAXN][MAXD],b[MAXD][MAXN],tot,sum[MAXD][MAXD];//转置矩阵不要写反了! inline int getint(){ int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w; } inline int calc(int x){ int tot=0; for(int i=1;i<=d;i++) for(int j=1;j<=d;j++) tot+=sum[i][j]*a[x][i]*a[x][j],sum[i][j]+=a[x][i]*a[x][j]; return tot%3; } inline void work(){ srand(20000605); n=getint(); d=getint(); k=getint(); for(int i=1;i<=n;i++) for(int j=1;j<=d;j++) a[i][j]=getint(),a[i][j]%=k; for(int j=1;j<=d;j++) for(int i=1;i<=n;i++) b[j][i]=a[i][j]; if(k==2) { int Case=0; while(Case<=5) {//随机几次 Case++; if(Case>5) break; tot=0; for(int i=1;i<=n;i++) c[i]=rand()%k,tot+=c[i]; tot%=k; for(int i=1;i<=n;i++) ans[i]=0; for(int i=1;i<=d;i++) { for(int j=1;j<=n;j++) ans[i]+=c[j]*a[j][i]; ans[i]%=k; } for(int i=1;i<=n;i++) c2[i]=0; for(int i=1;i<=n;i++){ for(int j=1;j<=d;j++) c2[i]+=ans[j]*b[j][i]; c2[i]%=k; } int tag=-1; for(int i=1;i<=n;i++) if(c2[i]!=tot) { tag=i; break; }//不同的位置 if(tag==-1) continue; int tag2=-1,now; for(int i=1;i<=n;i++) { if(i==tag) continue; now=0; for(int j=1;j<=d;j++) now+=a[tag][j]*b[j][i]; now%=k; if(now==0) { tag2=i; break; } } if(tag2!=-1) { if(tag>tag2) swap(tag,tag2); printf("%d %d\n",tag,tag2); return ; } } printf("-1 -1"); } else{ for(int i=1;i<=n;i++) q[i]=i; random_shuffle(q+1,q+n+1); int Case=1; while(Case--) { memset(sum,0,sizeof(sum)); for(int i=1;i<=n;i++) { if(calc(q[i])!=((i-1)%3)) { for(int j=1;j<i;j++) { int tot=0; for(int l=1;l<=d;l++) tot+=a[q[i]][l]*a[q[j]][l]; if(tot%3==0) { printf("%d %d",min(q[i],q[j]),max(q[i],q[j])); return ; } } } } } printf("-1 -1"); } } int main() { work(); return 0; }