问题:给定一个数组,该数组中的相邻的两个元素其二进制代码只在某个位上不同(类似于gray码),问能否在该数组中找到四个数,这四个数的异或的值是0。
限制:数组中的每个元素的取值范围是[0,2^64),数组的个数n的取值范围是[4,100000]
来源:CodeChef http://www.codechef.com/JULY12/problems/GRAYSC
关键字:抽屉原理/鸽笼原理
分析:
定义1 具有上述性质的数组我们称为A数组
性质1 A数组的相邻的元素异或之后的值只有一个bit为1。
性质2 如果存在i,使得B[i] = B[i+1],那么A[i] = A[i+2]。
定义2 计算A数组中任意两个相邻的数的异或的值,可以导出一个新的数组,该数组称为B数组。
定义3 计算A数组(0-index)中任意偶数index的元素与其后一个元素的异或的值,可以导出一个新的数组,该数组称为C数组。显然C数组是B的一个子数组。
性质3 如果A数组有n个元素,则B数组有n-1个元素,C数组有n/2个元素。
性质4 如果存在着i + 1 < j,使得B[i] == B[j],则(i,i+1,j,j+1)是一组解。对于C数组亦然。
性质5 B数组中的每个元素的二进制表示中只有一个bit是1。对于C数组亦然。
引理1 当n >= 130时,A数组必定有4个不同的元素,使得其异或的结果为0。
因为当n >= 130时,此时C数组的元素个数至少有65个。由于C数组元素的二进制表示只有一个bit是1,因此最多有64个不同的C元素。根据抽屉原理,即C数组中至少有两个数相同。 再根据性质4,必然有解。
引理2 当n >= 68时,A数组必定有4个不同的元素,使得其异或的结果为0。
因为n >= 68,所以B数组的元素个数至少有n-1 >= 67个,由于B数组元素的二进制表示只有一个bit是1,因此最多有64个不同的B元素,因此以下三种情况必居其一
(1)存在着i < j < k < p,使得B[i] == B[j] == B[k] == B[p],此时i + 1 <= j < k,根据性质4有解
(2)存在着i< j < k,p < q,使得B[i] == B[j] == B[k],B[p] == B[q],此时i + 1 <= j < k,根据性质4有解
(3)存在着i < j,p < q,u < v(i < p < u),使得B[i] == B[j],B[p] == B[q],B[u] == B[v]
(3.1)如果i + 1 < j 或 p + 1 < q 或 u + 1 < v,则根据性质4有解
(3.2)如果i + 1 == j且p + 1 == q且u + 1 == v 且i + 1 == p,则(i,i+2,p,p+2)是一组解
(3.3)如果i + 1 == j且p + 1 == q且u + 1 == v 且i + 2 < p,则(i,i+2,p,p+2)是一组解
(3.4)如果i + 1 == j且p + 1 == q且u + 1 == v 且p + 1 == u,则(p,p+2,u,u+2)是一组解
(3.5)如果i + 1 == j且p + 1 == q且u + 1 == v 且p + 2 < u,则(p,p+2,u,u+2)是一组解
(3.6)如果i + 1 == j且p + 1 == q且u + 1 == v 且i + 2 == p 且 p + 2 == u,则(i,i+2=p,p+2=u,u+2)是一组解
即当n >= 68时必定有解
方案一:n^4算法。
for(unsigned int i = 0;i < n;++i)
{
for(unsigned int k = i + 1;k < n;++k)
{
for(unsigned int p = k + 1;p < n;++p)
{
for(unsigned int q = p + 1;q < n;++q)
{
if(0 == (A[i]^A[k]^A[p]^A[q])) return true;
}
}
}
}
return false;
方案二:n^3*logn算法。
std::sort(A.begin(),A.end());
for(unsigned int i = 0;i < n;++i)
{
for(unsigned int k = i + 1;k < n;++k)
{
for(unsigned int p = k + 1;p < n;++p)
{
unsigned long long x = A[i]^A[k]^A[p];
if(std::binary_search(A.begin()+p+1,A.end(),x)) return true;
}
}
}
return false;
方案三:n^2或者n^2*logn算法