存档

‘Computer Science’ 分类的存档

海盗分金问题分析与解决

2010年8月2日 Zealot Comments

海盗分金问题分析与解决

——Nim取子问题变形后的解决方案

全文下载链接:海盗分金问题分析与解决.pdf

Author: Zealot Ke(kejie[at]kuxun.cn)
Date: 2010/7/30

背景

强哥邮件发出一个题目

  1. 有5个海盗,特别的残忍,特别的聪明
  2. 他们抢了100个金币,要进行分配,每个人都想分配足够多的金币,他们寸步不让.分配顺序是:5号海盗->4号海盗…1号海盗,即是:先由5号海盗决定如何分配,若分配不均,5号被扔下海,4号海盗再决定如何分配,依次类推.
  3. 如果没有二分之一或以上的海盗同意,分配人将被其他人扔下大海.
  4. 假如你是5号海盗,你会如何分配金币.确保自己不会被扔下大海,而且让其他4个海盗心服口服.

定义问题

5号海盗必须按照上述规则,找出最优分配方案,否则将被其他人扔下大海。

当前状态

正确分配方案还没出来,必须尽快找出最优解。

分析

典型的nim取子问题的变形,采用倒推方式即可找到最优解。

制定解决方案

倒推方法如下所示

  1. 如果只有一个海盗,如何分配?
  2. 如果有两个海盗,如何分配?
  3. 如果有n个海盗,每个海盗至少需要几个人同意?找出n-1个海盗情况下的分配方案中,需要收买多少海盗?最低需要多少金币?

实现解决方案

倒推方法实现如下表所示,每一行表示一种人数情况下的分配方案,也即,最后一行给出5号海盗的分配方案。

分配方案倒推分析表

分配方案倒推分析表

注意:从表中可以看出聪明是大前提,当然残忍也是。nim取子游戏向来是君子游戏,没有这个前提的话,后面的海盗不按套路出牌就不可能有最优解。

标准化解决方案

从上表不难看出规律,也即,自己拿尽可能多的金币,同时保证后面的人依次分配0、1交错的金币数。规范化命题如下
*命题: 假设对于n个聪明、残忍的海盗(n≥1),抢了Gold个金币,要进行分配,每个人都想分配足够多的金币,他们寸步不让。分配顺序是:n号海盗->n-1号海盗…1号海盗,即是:先由n号海盗决定如何分配,若分配不均,n号被扔下海,n-1号海盗再决定如何分配,依次类推。在该规则下,n号海盗最佳分配方案应当如此(为简单,避免使用地板函数,按n为奇偶数分类讨论):
不妨假设ones为得到1个金币海盗数,zeros为得到0个金币的海盗数。

  1. 当n为奇数时,ones=zeros=(n-1)/2,分配方案为Gold-ones,0,1…0,1
  2. 当n为偶数时,ones=( n-2)/2,zeros=n/2,分配方案为Gold-ones,0,1…1,0

该命题从上述分析中的规律中得出,不是定理,更不是公理,作为标准化解决方案需要严格证明。

证明:
注释:使用跳跃数学归纳法证明,跳跃的step为2。
先证明n为奇数时命题是否成立。
当n=1时,显然该命题成立
假设当n=m(m>1)时命题成立,则分配方案为
Ones(m) = Zeros(m) = (m-1)/2
Proposal(m) = Gold-Ones(m),0,1,…,0,1
= Gold-(m-1)/2, 0, 1, …,0,1
当n=m+1时,显然命题成立,理由如下
Ones(m+1) = (m+1-2)/2=(m-1)/2,Zeros(m+1) = (m+1)/2
注意:a) m+1为偶数,计算ones和zeros需要换个公式b) 对比n=m情形,仅仅是多了个0。
Proposal(m+1) = Gold-Ones(m+1),0,1,…,0,1,0
= Gold-(m-1)/2, 0, 1, …,0,1,0
对比Ones(m),Ones(m+1),Zeros(m),Zeros(m+1)可以看出仅仅是在Proposal(m)序列后面添个0。这会导致Proposal(m+1)与Proposal(m)两个序列中,0、1交错序列刚好错位,对应金币分配方案中则为:m+1号海盗分配方案就是把Proposal(m)中没有得到金币的人每人给1个金币实现自己金币最大化,同时保证同意的人数大于等于2。
综上所述,当n为奇数时,该命题成立。
再证明n为偶数时命题是否成立。
当n=2时,命题成立(大于的最小偶数只能是2)
归纳方法同1。
综合1、2分析,该命题成立。
证毕。

该标准化解决方案用程序实现如下
说明:该程序考虑了金币数量远小于海盗数量时,最先出来分配的部分海盗都得喂鲨鱼去了。

$ cat nim.c
#include 

const int g_first = 2;
const int g_zero = 0;
const int g_one = 1;

int print_proposal(int *gold, int *pirate, int *ones, int *zeros, int *status) {
        int             ret = 1;
        int             number = 0;
        int             remain = *gold - *ones;
        if (*pirate <= 0) {
                printf(".\n");
                return 0;
        }
        if (remain <= 0) {
                number = 0;     /* not enough gold, killed by others, regenerate proposal */
                ret = 1;        /* conitnue */

                /* regenerate proposal */
                if (*pirate % 2 == 0) {
                        *ones = (*pirate - 2) / 2;
                        *zeros = *pirate / 2;
                } else {
                        *ones = *zeros = (*pirate - 1) / 2;
                }
        } else if (*status == g_one) {
                number = 1;
                *ones--;
                *status = g_zero;
        } else if (*status == g_zero) {
                number = 0;
                *zeros--;
                *status = g_one;
        } else {
                number = remain;
                *status = g_zero;
        }
        if (*status != g_first && *ones <= 0 && *zeros <= 0) {
                ret = 0;
        }

        printf("%d ", number);
        *pirate = *pirate - 1;

        return ret;
}

int main()
{
        int             gold;
        int             pirate;
        int             ones;
        int             zeros;
        int             temp;
        int             status;         /* process first, zero or one */
        printf("Please Enter gold and pirate number:\n");
        while (scanf("%d %d", &gold, &pirate) == 2) {
                if (pirate < 1 || gold < 1)
                        break;

                if (pirate % 2 == 0) {
                        ones = (pirate - 2) / 2;
                        zeros = pirate / 2;
                } else {
                        ones = zeros = (pirate - 1) / 2;
                }

                status = g_first;
                printf("Proposal(%d, %d) is: ", gold, pirate);
                temp = pirate;
                while (print_proposal(&gold, &temp, &ones, &zeros, &status))
                        ;
                if (pirate == 1)
                        printf(".\n");

                printf("\n");
                printf("Please Enter gold and pirate number:\n");
        }
        return 0;
}

程序运行结果如下所示

$ gcc nim.c -o nim
$ ./nim
Please Enter gold and pirate number:
100 1
Proposal(100, 1) is: 100 .

Please Enter gold and pirate number:
100 2
Proposal(100, 2) is: 100 0 .

Please Enter gold and pirate number:
100 3
Proposal(100, 3) is: 99 0 1 .

Please Enter gold and pirate number:
100 4
Proposal(100, 4) is: 99 0 1 0 .

Please Enter gold and pirate number:
100 5
Proposal(100, 5) is: 98 0 1 0 1 .

Please Enter gold and pirate number:
0

决定下一步

本问题到此为止,暂时没有下一步动作。

总结

这个结论告诉我们

  1. 最先掌握分配权的人,看似最危险,胆小的人可能会担心所有人都不支持你,你就只能下海去喂鲨鱼了。其实,他优先掌握了主动权,天下武功,唯快不破,掌握市场先机很重要。
  2. 越快越好,但不能快过了头。100个金币,1000个海盗的话,最先出来分配的人必然死掉。同样的,20年前搞酒店搜索,率先占据主动权,快得一塌糊涂那也没戏。

完。

粗略读完《深入理解计算机系统》

2010年7月13日 Zealot Comments

书名:深入理解计算机系统(修订版)
英文名:Computer Systems A Programmer’s Perspective (简称CSAPP)
从英文名称就可以看出这是一本面向程序员的书籍。豆瓣上的链接:http://book.douban.co...

R61在linux/debian下安装madwifi无线驱动

2010年4月6日 Zealot Comments

本文记录Thinkpad R61i在debian lenny下安装madwifi驱动程序,替代ath5k,实现无线上网。
主要参考:http://forum.ubuntu.org.cn/viewtopic.php?f=116&t=169205。
仅作为tp安装linux的备忘,欢迎评论。

...

单件模式:Singleton那些事

2010年3月1日 Zealot Comments

单件模式确保一个类只有一个实例,并提供一个全局的访问点。 –《Head First 设计模式》
有些时候,我们恰好需要这样一种设计,保证我们实例化的类在进程地址空间内只有一个...

过度设计的典范: 新浪微博

2009年11月7日 Zealot Comments

(我的登录名是chzealot[at]gmail.com)
以下功能除非特别说明,都是在win7下Maxthon中测试(IE v8)
1. 登录框的suggestion
首先,suggestion是为用户提供便利的,而且可以把记忆的负担从...

Linux/Debian字体去掉锯齿的方法

2009年10月21日 Zealot Comments

Linux/Debian字体去掉锯齿的方法
在台式机上装了个Debian,但不知道怎么设置字体清晰,网上找了很多方法,都不好使,一直都很模
糊地讲究着。不过把字号稍微设置大点就不模糊了,...

配置hosts直接访问twitter

2009年10月10日 Zealot Comments

已失效,请另寻其他方法
67.220.213.23 twitter.com
67.220.213.23 www.twitter.com
168.143.162.101 assets1.twitter.com
168.143.162.101 assets0.twitter.com
168.143.162.1...

分类: web 标签: , , , , ,

读完梦断代码 – 做软件难

2009年6月24日 Zealot Comments

就像豆瓣上其他人评论的,这本书算是本奇书吧,OSAF团队有时间、有资金、有牛人,最终历经6年打造出1.0版,并行将就木。梦幻的开局,让人唏嘘不已的结局,其中的故事会是怎样呢?Scott ...

apt无法卸载nginx的问题

2009年4月29日 Zealot Comments

今天小试了一把nginx,用apt装上后开始配置,可对着中文wiki上的文档配置时总是超出预期,出现各种问题,突然想起查看一下版本,竟然是0.4,官方已经0.7了,faint
于是用apt卸掉,结果ap...

C++拷贝构造函数没有执行的问题

2009年4月29日 Zealot Comments

问题是在这http://lilacbbs.com/forum/d.php?bid=66&id=62395,想了半天想不明白
摘要就是 Fred z = Fred(3);时没有执行拷贝构造函数,查了标准手册才发现原来如此:会不会调用拷贝构造函数由具体...