[NOIP2003]侦探推理 详解+python实现

题意

M个人一起玩侦探游戏,其中有一名是凶手(其他人不知情),现在你要通过这些人的证词查明谁是凶手。证词有以下几种类型:

  1. 我是凶手
  2. 我不是凶手
  3. XXX是凶手
  4. XXX不是凶手
  5. 今天是星期几,如一、二、三、四、五、六、日
  6. 其他,如天气真好(该类证词无效,无需推理)

每个人可能有多句证词,但已知其中有N个人始终说真话,剩下的人始终说假话。具体是哪N个人未知。

请你协助破案,推断出谁是凶手。请记住,凶手只有一个。

如果判断出不止一个人可能是罪犯,则输出 “Cannot Determine”;如果程序判断出没有人可能成为罪犯,则输出 “Impossible”。

数据范围

M<=20,证词数<=100。

样例数据

3 1 5
MIKE
CHARLES
KATE
MIKE: I am guilty.
MIKE: Today is Sunday.
CHARLES: MIKE is guilty.
KATE: I am guilty.
KATE: How are you??
MIKE

分析

正确理解题目

证词的含义

  1. 指认XXX是凶手:若是真话,凶手就是XXX;若是假话,XXX不是凶手,凶手是XXX之外的某一个人。
  2. 指认XXX不是凶手:若是真话,XXX不是凶手,凶手是XXX之外的某一个人;若是假话,凶手是XXX。
  3. 今天是星期几:一开始没明白为什么要有这种跟凶手是谁无关的话,然后意识到这可以辅助判断说的是真话还是假话。比如一个人说今天是星期二,另一个人说是星期三,那么其中有人说假话。

注意题目中说“有的人始终说真话,剩下的人始终说假话”,意思是一个人的证词要么全部为真,要么全部为假。注意,不在上述类型的证词,可以算是真话,也可以算是假话。

另外注意,说假证词跟是凶手没有半毛钱关系。

推理的正确性

假设两个侦探都在推理这个案子(假设已知一人说假话),一个侦探发现当A、B、C三人说真话、D说假话时,可以认定A是凶手,另一个侦探发现当B、C、D三人说真话、A说假话时,可以认定D是凶手。到底A和B哪个是凶手呢?

都不是。准确的推理满足,无论哪N个人说真话,都要推断得出一致的凶手。(只有一位怀疑的凶手,且被怀疑的凶手是同一人)

上述情况不满足“凶手是同一人”这一条件,所以结果是:Cannot Determine。

算法

单从证词去推理是不可行的,因为不知道谁说了真话、谁说了假话,不能把真话、假话放在一起推理。

要是知道谁说了真话、谁说了假话,就好办多了,自然想到枚举。但是从20个人中枚举若干个人,时间复杂度是阶乘级别,不可接受。(假如真要这么做,应该答案也是正确的,只是一定要记得判断枚举出的方案有没有自相矛盾的情况,比如两个说真话的冲突、两个说假话的冲突、说真话的和说假话的冲突,遇到自相矛盾的方案要跳过)

这时自己想到了二分图,把每个人拆成两个点,说真话是一个点,说假话是另一个点……但想了一会儿后,就没有继续想下去。

后来想到自己平常推理的习惯,先会假设嫌疑人A是不是凶手,再假设嫌疑人B是不是凶手……忽然发现假设凶手的话,判断证词真假变得异常简单,也能立刻判断是否满足题目条件。也就是说,要反过来做,通过枚举答案来做。(通过M的数据范围非常小的特点,也容易想到枚举答案这种思路)(或者,题目中要我们考虑有多个人可能是凶手的情况,也就是提示需要论证每个人是凶手的可能性,也能想到枚举凶手)(或者,考虑到案件真相只有M×7种情况,而真相只有一个,也就想到枚举真相)

想到这里就简单了:通过枚举凶手+星期,判定每个玩家说的话是真是假,若真假的人数满足题目要求,则表示这个凶手有可能,最后根据有可能的凶手数量确定结论。 只有一个可能凶手的话,那凶手一定是这个人。

注意,俗话说“真相只有一个”,但在这道题目里只考虑凶手的唯一性。哪怕一个凶手在多天都有犯案可能,只算一次(凶手唯一,作案日期不唯一)。

注意,有些人的证词判断不出真假(甚至有人没有证词),他们既可以算说真话的,也可以算说假话的,可以灵活调整。只要把判断合法条件设置为,确定说真话人数<=M-N and 确定说假话人数<=N即可。

其实这种反向做法和枚举真假的做法是相通的,只是在求证词+真假情况=凶手等式的两个不同成分,枚举空间的量级不一样。

复杂度

O(MP)

代码

这题的一个难点是字符串处理,用C写太麻烦了。人生苦短,我用python。

另注意最后判断Cannot Determine和Impossible的逻辑。

import sys

WEEKDAYNAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

def readintegers()
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值