M个人一起玩侦探游戏,其中有一名是凶手(其他人不知情),现在你要通过这些人的证词查明谁是凶手。证词有以下几种类型:
每个人可能有多句证词,但已知其中有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
注意题目中说“有的人始终说真话,剩下的人始终说假话”,意思是一个人的证词要么全部为真,要么全部为假。注意,不在上述类型的证词,可以算是真话,也可以算是假话。
另外注意,说假证词跟是凶手没有半毛钱关系。
假设两个侦探都在推理这个案子(假设已知一人说假话),一个侦探发现当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]