趣题:尽可能用奇数次猜测完成猜数游戏

    现在,我在心里想一个不超过n的正整数t。你的任务是尽可能用奇数次猜测猜中这个数(你知道n是多少)。每次猜测后,我都会告诉你你所做的猜测是大了还是小了。你不能猜测已经被排除了的数(来消耗猜测次数),你的每次猜测都必须符合我原来给出的回答。你觉得,你获胜(奇数次猜中)的几率有多大?

 
    动态规划的几个类似的经典模型启发了我们:设a[m]表示采取最优策略后在m个数里猜奇数次猜中的概率,b[m]表示如果题目要求我们猜偶数次,那最优策略下有m个数时获胜的概率是多少。考虑现在我有m个数可以猜,我想在奇数次内猜中。现在我猜的是数字i。狗屎运最好时,我一次猜中直接就赢了,它的概率是1/m;有(i-1)/m的情况下我会得到“大了”的提示,这样的话我需要用偶数次猜测去猜前面那i-1个数;剩余的那(m-i)/m的情况中,我需要用偶数次猜测去猜m-i个数。因此,a[m] = Max {1/m + (i-1)/m * b[i-1] + (m-i)/m * b[m-i], 1≤i≤m} 。类似地,我们也可以得出b[m]的递推公式:b[m] = Max {(i-1)/m * a[i-1] + (m-i)/m * a[m-i], 1≤i≤m} 。
    学习使用Mathematica确实是一件好事,你可以用Mathematica非常方便地描述出我们上面的两个递推公式,不需要自己去写那些冗长的程序了。
a[m_] := Max[Table[1/m + (i-1)/m * b[i-1] + (m-i)/m * b[m-i], {i, m}]]; a[0] := 0;
b[m_] := Max[Table[(i-1)/m * a[i-1] + (m-i)/m * a[m-i], {i, m}]]; b[0] := 0;

Read more…

趣题:用位运算生成下一个含有k个1的二进制数

    在所有8-bit的整数中,含有k个数字“1”的二进制数一共有C(8,k)个。给出其中的一个二进制数,你如何利用位运算快速找到下一个恰有k个“1”的数?例如,如果给你二进制数01011100,那么下一个(含4个“1”的)数就是01100011。在继续阅读下去之前,建议你仔细思考一下。你或许会想看看我很早以前写的一篇介绍位运算的文章。这是一道很好的题目,很多位运算技巧在这里都有体现。

    在草稿纸上随便举几个例子,规律很容易看出来。由于“1”的个数是固定的,为了让这个二进制数更大,我们必须把第一个出现在“1”左边的“0”改成“1”;同时,为了让这个二进制数尽可能小,我们必须把它右边那些“1”重新排到最低位去。
    更具体地说,下一个二进制数可以通过以下步骤得到:找到右起第一个单个的或连续的数字“1”,把它们全改成“0”,同时把它们左边的那个“0”改为“1”。此时,“1”的个数可能减少了,我们只需把还差的“1”摆在最右边就行了。举个例子,01011100的右起第一个“1”在第三位,把它和左边紧挨着的“1”一并变为“0”,并把再左边那个“0”变为“1”,于是我们得到01100000。我们还差两个“1”,把这两个“1”补在最低位得到01100011即可。现在我们的任务是,想出一个用位运算来实现这些步骤的办法。
    我们已经熟知,用x & -x可以提取最右边的那个“1”。当意识到可以利用加法来消除连续的“1”时,我们很快得到了第一步操作的位运算实现:把x & -x加到x上,利用二进制加法的进位把“..01111..”变成“..10000..”。现在,我们需要计算出刚才的操作中一共“跳过”了多少个“1”,换句话说现在的x的右起第一个“1”和原来的x的右起第一个“1”差了多少位。关键就在这里!我们可以用除法来完成这一步,例如100000除以100就相当于把被除数右移2位,得到的结果即可以表示两个数中的“1”差了多少位。在最低位产生指定数量的“1”需要用到另一个技巧:减1操作可以把右边连续的“0”都变成“1”,即把…10000变成…01111。我们得到了该问题的第一个算法:

b = x & -x;
t = x + b;
c = t & -t;
m = (c/b >> 1) - 1;
r = t | m; //最终结果

    我们对上述算法做一个简单的说明:

操作              | 样例     |  说明
——————+———-+—————————-
x                 | 01011100 |  原数
b = x & -x        | 00000100 |  提取x的右起第一个“1”
t = x + b         | 01100000 |  把x的右起第一个位于某个“1”左边的“0”变成“1”,并把它右边的那些“1”都变为“0”
c = t & -t        | 00100000 |  提取t的右起第一个“1”
c / b             | 00001000 |  右移c中的那个“1”,其结果中最低位连续的“0”的个数正好是c和b中的“1”相差的距离
m = (c/b >> 1) – 1| 00000011 |  在最低位产生数字“1”,其个数比上述的“距离”少1
r = t | m         | 01100011 |  最终结果

    除去赋值,我们一共用了9个运算符。有可能用更少的运算么?

Read more…

趣题:奇怪的有向图 任两点间两步之内可达的路径有且仅有一条

    有这么一个无自环的有向图,它的顶点数在30和40之间(包括30和40)。对于图里面的任意两个点A和B,要么存在一条有向边A->B,要么存在唯一的一个“中间点”C使得A可以通过A->C->B两步走到B。换句话说,对任意给定的A、B两点,从A到B的长度不超过2的路径有且仅有一条。注意,即使当A=B时,这个条件也是成立的。试问这个图有多少个顶点。

 
Read more…

八皇后加强版:每个皇后最多攻击一个其它的皇后

    想必搞OI/ACM的朋友都应该知道八皇后问题,这是学习编程的必修课程之一:在国际象棋棋盘上最多可以放置多少个互不攻击的皇后(皇后可以攻击它所在的行、列、对角线方向上的棋子)?显然,能够放置的皇后数不超过8个,因为国际象棋的棋盘一共就8行8列。事实上,放置8个互不攻击的皇后是有可能的,并且方法不止一种。
    上个月的IBM Ponder This考虑了一个八皇后问题的扩展:最多可以在国际象棋棋盘上放置多少个皇后,使得每一个皇后最多只能攻击到一个其它的皇后?

Read more…

趣题:2n+1个点中任n个都与同一点相连,则存在一个连接所有点的点

    有2n+1个人,他们的朋友关系满足这样一种奇特的性质:任选n个人,则在剩下的人中总能找到一个人,他和这n个人都是朋友。求证,存在这样一个人,他和所有人都是朋友。我们假设朋友关系是双向的,也就是说如果A是B的朋友,那么B一定是A的朋友。

    这明显可以转化为一个图论问题。选出两个互为朋友的人。我们得到了一个大小为2的团(一个“团”就是一个所有结点两两相连的子图,或者干脆说是完全图形状的子图)。和另外n-2个人并在一起,则存在一个人和他们都是朋友(当然和那两个人也就是朋友了)。把这个人加进刚才那个团里,于是我们得到了一个大小为3的团。又随便取n-3个人和这3个并在一起,则有一个人和所有这些人都是朋友,于是我们继续扩展出一个大小为4的团。反复进行这个操作,直到我们得到一个大小为n+1的团,此时团的大小已经不能再继续扩展了。但是,一旦注意到此时不属于团的人只剩n个了时,我们发现问题已经解决了:在团里面存在一个人P,他和不属于这个团的那n个人都是朋友。而P本身在一个大小为n+1的团里面,他和团里的其余n个人都是朋友。因此,P和所有人都是朋友。

来源:http://www.cut-the-knot.org/arithmetic/combinatorics/Clique.shtml