O(1)求解自然数异或和
序
又是一个不眠之夜。
求:
思路1:周期分析
\(O(logn)\)算法
考虑按位分析
对于\(f_i\)的第\(j\)位,它的值只与该位1出现次数有关。
而第\(j\)位1的出现又是呈周期性分布的。
我们考虑\(f_i=0 \bigoplus 1 \bigoplus 2 \bigoplus 3 \bigoplus...\bigoplus (i-1) \bigoplus i\)。
注意这里多加了一个0。
那么,在上式的各数中,第1位的变化为01010101
而第2位为00110011
第3位为00001111
以此类推。
周期分析
所以我们可以发现,第\(j\)位的值的出现是连续的,且每连续一组的相同值的个数为\(2^{j-1}\),这恰好是第\(j\)位的位权!
而对于数字的总个数,我们可以用\(x=i+1\)来表示。
分析第\(j\)位的值\((j \ge 2)\):
则第\(j\)位的出现的整组共有\(t=\lfloor{{x}\over{2^{j-1}}}\rfloor\)个,其中奇数组为0,偶数组为1,且其中出现数字1的总个数必定为偶数。
若\(t\)为奇数,说明剩余的不完整组的值为1,同时若\(x\)也为奇数,说明\(f_i\)的第\(j\)位为1;否则\(f_i\)的第\(j\)位为0。
由此,我们可以得到第\(j\)位的值\((j \ge 2)\)。
对于第1位,它出现的组共有\(x\)个,其中值为1的有\(\lfloor{{x}\over{2}}\rfloor\)个,故\(f_i\)的第1位等于\(x\)的第2位。
综上可以在\(O(logn)\)时间复杂度内求解。
\(O(1)\)算法
其实就是对\(O(logn)\)的算法作了一个小的总结。
分析第\(j\)位的值\((j \ge 2)\):
我们知道,当且仅当\(t = \lfloor{{x}\over{2^{j-1}}}\rfloor\)为奇数,同时\(x\)也为奇数时,第\(j\)位才为1;否则第\(j\)位为0。
体现在\(x\)这个数本身上,就是当\(x\)第1位为1时,\(f_i\)的第2位及以上与\(x\)的相同。
而当\(x\)第1位为0时,\(f_i\)的第2位及以上都为0。
然后第1位的特判很好处理,就是\(f_i\)的第1位等于\(x\)的第2位。
由此可以在\(O(1)\)时间复杂度内求解。
代码实现
闲来无事写个代码(因为太菜所以不会更简单的写法)
int xorsum(int x)
{
++x;
return ( (x&1) ? (x&(INT_MAX-1)) : 0 ) | ( (x&2) ? 1 : 0 );
}
数据检验
顺便学了一下二进制输出的方法
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<bitset>
using namespace std;
int xorsum(int x)
{
++x;
return ( (x&1) ? (x&(INT_MAX-1)) : 0 ) | ( (x&2) ? 1 : 0 );
}
int main()
{
int n=10;
for(int i=1;i<=n;++i)
{
cout<<"number:"<<bitset<sizeof(i)*8>(i)<<'\n';
cout<<"xorsum:"<<bitset<sizeof(i)*8>(xorsum(i))<<'\n';
}
return 0;
}
输出如下:
思路2:数学化简
看了ltao思路和Limit给的正解,发现自己的做法实在是......太菜了。
一个很明显的劣势在于:我们的周期性分析是以\(x=i+1\)算出总数字个数再来分析性质的。
这样的转义分析最大的缺点就在于,性质推广,或者说移植的时候,会将定义转来转去,非常难以处理。
所以在这里再整理一下\(ltao\)的思路:
核心性质
定义\(g_{i,j}=i \bigoplus (i+1) \bigoplus ... \bigoplus j\)
有性质:\(g_{0,2^{k}-1} = g_{2^{k},2^{k+1}-1}(k \ge 1)\),即右式第\(k+1\)位全部被异或消掉
将左右两式异或可得:\(g_{0,2^{k+1}-1} = g_{0,2^{k}-1} \bigoplus g_{2^{k},2^{k+1}-1} = 0\)
得到核心性质:\(g_{0,2^{k-1}-1} = 0(k \ge 3)\),即可以用这个性质消掉函数中第\(k\)位为0的所有数,注意这个\(k\)的边界很重要!
\(O(logn)\)算法
有了这个性质,我们就可以很方便地对原式最高位进行化简,设所求函数\(g_{0,i}\)的\(i\)的最高位为\(k\),有:
\(g_{0,i} = g_{0, 2^{k-1}-1} \bigoplus g_{2^{k-1},i} = g_{2^{k-1},i}\)
然后组成\(g_{2^{k-1},i}\) 的数的第\(k\)位都为1,且共有\(m = i - 2^{k-1} + 1\)个这样的数
这样就可以化简掉第\(k\)位:
若\(i\)为奇数,则\(m\)为偶数,结果的第\(k\)位必然为0,即\(g_{2^{k-1},i} = g_{0,i-2^{k-1}}\);
若\(i\)为偶数,则\(m\)为奇数,结果的第\(k\)位必然为1,即\(g_{2^{k-1},i} = g_{0,i-2^{k-1}} + 2^{k-1}\)。
递归处理,至\(k \le 2\),达到了我们上面所说性质的边界以外,特判即可。
由此可以在\(O(logn)\)时间复杂度内求解。
\(O(1)\)算法
然后再对这个算法作一个小的总结:
我们可以发现上式的\(g_{0,i}-> g_{0,i-2^{k-1}}\)的过程当中,\(i\)的奇偶性始终不变。
因此只需一次分析起始状态\(g_{0,i}\):
若\(i\)为奇数,则\(m\)为偶数,结果的第3位及以上都为0。
若\(i\)为偶数,则\(m\)为奇数,结果的第3位及以上与\(i\)的相同。
剩下两位特判即可。
由此可在\(O(1)\)时间复杂度内完成求解。
代码实现
int f[4]={0,1,3,0};
int xorsum(int x)
{
return ( (x&1) ? 0 : (x&(INT_MAX-3)) ) | f[x&3];
}
或者更直观的写法:
int xorsum(int x)
{
switch(x&3)
{
case 0:
return x;
case 1:
return 1;
case 2:
return x+1;
case 3:
return 0;
}
}
非常重要的一点在于,这种直接将x&3的值和xorsum的值对应的直观函数表示,能够有效地解决一些扩展问题,有兴趣的可见这道基于Limit's idea的水题。