醉汉看守是这样的一个问题:有N个上了锁的牢记。看守要喝N杯威士忌,每喝一杯他就执行一个这样的操作:按规则 R 遍历所有的牢房,如果门是开着的就锁上,如果门是锁着的就打开。遍历规则 R 是:喝第 i 杯威士忌时,仅遍历 i , 2*i , 3 * i , .... 牢房。问,在他喝完N杯威士忌后,有多少个牢房是开着的?
模拟这个过程就是,第一次喝酒后,所有牢房都开了;第二次喝酒后,2,4,6,8,10...牢房与上一次相反,其它牢房维持上一次状态;第三次喝酒后,3,6,9牢房与上一次相反,其它牢房维持一致;........
每一个牢房在N次操作后是否开着,取决于它所有小于N的因子(包括1和自身)的个数,若个数为奇数,则它与初始状态相反,即开着,如果个数为偶数,则它与初始状态相同,即关着。
每一个数字存在一个因子,必有另一个因子,即因子的出现总是成对的。只有一个数字为平方数时才不是成对出现的,因为两个因子都是它,所以只能算一个。将这个结论运用到上面的模型中就是,对于编号为 i 的牢房,在第 q 次操作时会遍历到它,这个 q 是 i 的因子。设 i = p * q,则不仅第 q 次操作会遍历到 i 号牢房,而且第 p 次操作也会遍历到 i 号牢房,这两次操作的作用被抵消了(关关为开,开开为关)。而若 i 还存在一对相同的因子a,即 i = a * a,则第 a 次操作会改变 i 号牢房,而且只会被改变一次!
所以,问题转化为,找出比 N 小的所有正整数里面平方数的个数即可。使用 floor(sqrt(N))即能得到结果。
普通的解法是维护一个 N 维的数组,0表示关,1表示开,有两层循环来模拟醉汉模型即可得出最终结果,核心循环为:
int state[N] ;
memset(state,0,N);
for(int i = 0;i < N;i ++)
{
for (int j = i;j < N;j += j)
{
state[j] = 1 - state[j];
}
}