[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():
    while True:
        s = sys.stdin.readline()
        if s == None:
            return None
        if s.rstrip():
            return map(int, s.rstrip().split(" "))

def parsepredicate(s):
    name, sentence = s.split(": ", 1)
    assert name in names
    if sentence == "I am guilty.":
        return (name, name, "guilty")
    elif sentence == "I am not guilty.":
        return (name, name, "notguilty")
    elif sentence.endswith(" is guilty."):
        objective = sentence.split(" ")[0]
        assert objective in names
        return (name, objective, "guilty")
    elif sentence.endswith(" is not guilty."):
        objective = sentence.split(" ")[0]
        assert objective in names
        return (name, objective, "notguilty")
    elif sentence.startswith("Today is "):
        dayname = sentence.split(" is ")[1].split(".")[0]
        assert dayname in WEEKDAYNAMES
        return (name, None, "day", dayname)
    else:
        #print "Invalid predicate:", s
        return (name, None, None)

def testpredicate(predicate, criminal, weekday):
    if predicate[2] == "guilty":
        return criminal == predicate[1]
    elif predicate[2] == "notguilty":
        return criminal != predicate[1]
    elif predicate[2] == "day":
        return weekday == predicate[3]
    else:
        return None

while True:
    M, N, P = readintegers()
    names = [sys.stdin.readline().strip() for i in range(M)]
    predicates = [parsepredicate(sys.stdin.readline().strip()) for i in range(P)]
    #print predicates

    possiblecriminals = set()
    for criminal in names: # test if anyone is possibly a criminal
        for weekday in WEEKDAYNAMES:
            print "Test case: Criminal is " + criminal + ", and today is " + weekday
            credits = {}
            impossbile = False
            for predicate in predicates:
                # test if it is true under this case
                speaker = predicate[0]                
                result = testpredicate(predicate, criminal, weekday)
                if speaker not in credits and result != None:
                    credits[speaker] = result
                elif speaker in credits and result != None and result != credits[speaker]:
                    credits[speaker] = "contradicted"
                    impossbile = True                    
                    break
            print "-> ", credits
            if not impossbile and credits.values().count(True) <= M-N and credits.values().count(False) <= N:
                print "! " + criminal + " is possible"
                possiblecriminals.add(criminal)
                #break

    if len(possiblecriminals) == 0:
        print "Impossible"
    elif len(possiblecriminals) > 1:
        print "Cannot Determine"
    else:
        print list(possiblecriminals)[0]

你可能感兴趣的:(oi竞赛)