Skip to content

M02692: 假币问题

brute force, http://cs101.openjudge.cn/pctbook/M02692/

赛利有12枚银币。其中有11枚真币和1枚假币。假币看起来和真币没有区别,但是重量不同。但赛利不知道假币比真币轻还是重。于是他向朋友借了一架天平。朋友希望赛利称三次就能找出假币并且确定假币是轻是重。例如:如果赛利用天平称两枚硬币,发现天平平衡,说明两枚都是真的。如果赛利用一枚真币与另一枚银币比较,发现它比真币轻或重,说明它是假币。经过精心安排每次的称量,赛利保证在称三次后确定假币。

输入

第一行有一个数字n,表示有n组测试用例。 对于每组测试用例: 输入有三行,每行表示一次称量的结果。赛利事先将银币标号为A-L。每次称量的结果用三个以空格隔开的字符串表示:天平左边放置的硬币 天平右边放置的硬币 平衡状态。其中平衡状态用up'', down'', 或 ``even''表示, 分别为右端高、右端低和平衡。天平左右的硬币数总是相等的。

输出

输出哪一个标号的银币是假币,并说明它比真币轻还是重(heavy or light)。

样例输入

1
ABCD EFGH even 
ABCI EFJK up 
ABIJ EFGH even

样例输出

K is the counterfeit coin and it is light.

来源:East Central North America 1998,计算概论05

2023年9月8日写了一遍穷举方法。

只有一个是假币,因此我们可以考虑让每个都当一次假币,然后假设他是重的还是轻的,验证就可以了。

python
n = int(input())

def check(coins, case):
    for item in case:
        left, right, res = item.split()

        left_total = sum(coins[i] for i in left)
        right_total = sum(coins[i] for i in right)

        if left_total == right_total and res != 'even':
            return False
        elif left_total < right_total and res != 'down':
            return False
        elif left_total > right_total and res != 'up':
            return False

    return True

for _ in range(n):
    case = [input().strip() for _ in range(3)]

    for counterfeit in 'ABCDEFGHIJKL':
        found = False
        for weight in [-1, 1]:
            coins = {coin: 0 for coin in 'ABCDEFGHIJKL'}
            coins[counterfeit] = weight

            if check(coins, case):
                found = True
                tag = "light" if weight == -1 else "heavy"
                print(f'{counterfeit} is the counterfeit coin and it is {tag}.')
                break
        if found:
            break

解题思路:在题目描述中,已经明确保证三次称重后能够确定假币,即输入的三组称量数据有唯一的答案。硬币有三种状态:较重的假币、较轻的假币、真币。由于只有1枚假币,且总共只有12枚银币,因此可以对所有的情况进行枚举。假币可能是任意一枚,有12种情况:且假币可能比真币重,也可能比真币轻,有两种情况。在这全部的24种情况当中,只有一种情况能够符合三组称量数据,这就是所要寻找的答案。

​ 假设用0、-1和1表示真币、较轻币和较重币的重量。分别计算天平左侧与右侧的重量,若下面三个条件中有一个不满足,则说明假设不成立,当前的情况与称量数据矛盾。

​ (1)如果天平左侧较重,且称量结果为up.

​ (2)如果天平右侧较重,且称量结果为down.

​ (3)如果天平左右两侧重量相同,且称量结果为even.

解题思路:可以把银币分类,真币的重量为 0,轻假币为-1,重假币为 1,然后把秤变为条件,依次对所有银币假设为假币,总共有 24种情况,遍历一遍就可以了。如果通过三个秤(条件),就输出当前的银币。

python
status = [0]*12
left = ['' for _ in range(3)]
right = ['' for _ in range(3)]
result = ['' for _ in range(3)]

def Balanced():
    for j in range(3):
        leftW = rightW = 0
        for k in range(len(left[j])):
            leftW += status[ord(left[j][k]) - ord('A')]
            rightW += status[ord(right[j][k]) - ord('A')]
    
        if leftW>rightW and result[j][0]!='u':
            return False
        if leftW == rightW and result[j][0]!='e':
            return False
        if leftW<rightW and result[j][0]!='d':
            return False
    
    return True

for _ in range(int(input())):
    for i in range(3):
        left[i], right[i], result[i] = input().split()
    
    for i in range(12):
        status[i] = 0
    
    i = 0
    for i in range(12):
        status[i] = 1
        if Balanced():
            break
        
        status[i] = -1
        if Balanced():
            break
        
        status[i] = 0
    
    print('{} is the counterfeit coin and it is {}.'.format( chr(i+ord('A')), \
                    "heavy" if status[i]>0 else "light"))

2020fall-cs101,李逸晨

解题思路:2020fall-cs101,方磊。参考了李逸晨大佬的代码,觉得大佬的思路真的非常清晰而且解法应该是题解中最好的。

对一个轻的假币来说(重的同理),在称量中,只要它在天平左边天平右边就会下降,在天平右边天平右边就会上升,不在天平上天平两边就会平衡。只有轻的假币才会满足这种特性。

如果三个式子都成立,那么这就是假币。

python
# https://stackabuse.com/any-and-all-in-python-with-examples/
# The method any(iterable) behaves like a series of or operators between 
#   each element of the iterable we passed.
# The all(iterable) method evaluates like a series of and operators between 
#   each of the elements in the iterable we passed.

for _ in range(int(input())):
    L = [[],[],[]]
    for i in range(3):
        L[i] = input().split()
    for f in 'ABCDEFGHIJKL':
        if all((f in i[0] and i[2]=='up') or (f in i[1] and i[2]=='down') 
               or ( f not in i[0] + i[1] and i[2]=='even') for i in L):
            print("{} is the counterfeit coin and it is {}.".format(f,'heavy'))
            break
        if all((f in i[0] and i[2]=='down') or (f in i[1] and i[2]=='up') 
               or (f not in i[0]+i[1] and i[2]=='even') for i in L):
            print("{} is the counterfeit coin and it is {}.".format(f,'light'))
            break
python
s="ABCDEFGHIJKL"
for _ in range(int(input())):
    p=[list(input().split()) for _ in range(3)]
    for i in s:
        if all((i in j[0] and j[2]=="up") or (i in j[1] and j[2]=="down") or (i not in j[1] and i not in j[0] and j[2]=="even") for j in p):
            print(f"{i} is the counterfeit coin and it is heavy.")
            break
        elif all((i in j[0] and j[2]=="down") or (i in j[1] and j[2]=="up") or (i not in j[1] and i not in j[0] and j[2]=="even") for j in p):
            print(f"{i} is the counterfeit coin and it is light.")
            break

这个题老早就做过了,当时只会if else,但是这里我要推荐一下我的大佬室友(不是我们班的)的超强思路(我认为): 开两个列表,记录每个硬币被怀疑的次数。如果even那么肯定是正常的,就+-一个很大的数就行了(这里我用的是114514),然后最后输出被怀疑次数最多的呢个硬币并且看看最多的是在qing还是zhong列表里,对应着假币本身的轻重

python
# 室友是大佬,24-工学院
def main():

    l= {0:'A',1:'B',2:'C',3:'D',4:'E',5:'F',6:'G',7:'H',8:'I',9:'J',10:'K',11:'L'}
    l1= {'A':0,'B':1,'C':2,'D':3,'E':4,'F':5,'G':6,'H':7,'I':8,'J':9,'K':10,'L':11}
    s1=[]
    s2=[]
    s=[]
    qing=[0]*12
    zhong=[0]*12
    for i in range(3):
        shuru=input().split()
        s1.append(shuru[0])
        s2.append(shuru[1])
        s.append(shuru[2])
    for i in range(3):
        if s[i]=='even':
            for j in s1[i]:
                qing[l1[j]]=-114514
                zhong[l1[j]] = -114514
            for j in s2[i]:
                qing[l1[j]] = -114514
                zhong[l1[j]] = -114514
    for i in range(3):
        if s[i]=='up':
            for j in s2[i]:
                qing[l1[j]]+=1
            for j in s1[i]:
                zhong[l1[j]]+=1
        if s[i]=='down':
            for j in s1[i]:
                qing[l1[j]]+=1
            for j in s2[i]:
                zhong[l1[j]]+=1
    q=s.count('down')+s.count('up')
    for i in range(12):
        if qing[i]==q:
            print(l[i],' is the counterfeit coin and it is light.',sep='')
            break
        if zhong[i]==q:
            print(l[i], ' is the counterfeit coin and it is heavy.', sep='')
            break
n=int(input())
for i in range(n):
    main()

思路:很久之前做的,分类讨论进行实现即可,可以利用集合的运算以及字典优化过程。(心目中的brute force No.2,第三是装箱,第一是熄灯问题)

python
# 李承容 物理学院 2400011499
n=int(input())
for _ in range(n):
    coins=set("ABCDEFGHIJKL")
    dic={}
    for i in range(3):
        a,b,c=map(str,input().split())
        if c=="even":
            coins=coins-set(a)-set(b)
        elif c=="up":
            for i in range(4):
                if a[i] in dic:
                    if dic[a[i]]=="light":
                        coins-=set(a[i])
                else:
                    dic[a[i]]="heavy"
                if b[i] in dic:
                    if dic[b[i]]=="heavy":
                        coins-=set(b[i])
                else:
                    dic[b[i]]="light"
            coins=coins & (set(a)|set(b))
        else:
            for i in range(4):
                if a[i] in dic:
                    if dic[a[i]]=="heavy":
                        coins-=set(a[i])
                else:
                    dic[a[i]]="light"
                if b[i] in dic:
                    if dic[b[i]]=="light":
                        coins-=set(b[i])
                else:
                    dic[b[i]]="heavy"
            coins=coins & (set(a)|set(b))
    res="".join(coins)
    print(f"{res} is the counterfeit coin and it is {dic[res]}.")

思路:假币在不平衡的情况下应该归属于同一边,且其不属于平衡结果中的部分,所以用集和处理

python
# 王梓航、24物理学院
n = int(input())
for _ in range(n):
    d, e, f = set('ABCDEFGHIJKL'), set('ABCDEFGHIJKL'), set()
    for j in range(3):
        a, b, c = input().split()
        if c == 'even':
            f = f | set(a + b)
        elif c == 'up':
            d = d & set(a)
            e = e & set(b)
        else:
            d = d & set(b)
            e = e & set(a)
    g = d & f
    h = e & f
    e -= h
    d -= g
    if d:
        s = list(d)[0]
        print('{} is the counterfeit coin and it is heavy.'.format(s))
        continue
    s = list(e)[0]
    print('{} is the counterfeit coin and it is light.'.format(s))

使用了集合的运算,初始化重(heavy)和轻(light)两个集合,代表所有可能的重假币和轻假币,之后根据条件取交集或者补集,逐渐缩减范围,最后取两个之中的非空集合

python
# 刘人豪 24物理学院
n = int(input())
s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'}
for j in range(n):
    heavy = s.copy()
    light = s.copy()
    for _ in range(3):
        a = input().split()
        if a[2] == 'even':
            heavy = heavy - set(a[0]) - set(a[1])
            light = light - set(a[0]) - set(a[1])
        elif a[2] == 'up':
            heavy &= set(a[0])
            light &= set(a[1])
        elif a[2] == 'down':
            heavy &= set(a[1])
            light &= set(a[0])
    x = str(*(heavy ^ light))
    if x in light:
        print(f'{x} is the counterfeit coin and it is light.')
    else:
        print(f'{x} is the counterfeit coin and it is heavy.')

思路:之前做过一次,用的是枚举。然后现在又做了一次,感觉大脑中对枚举已经有了天然的恐惧,所以就又想了一个不枚举的方法,与群里面的那位大佬不谋而合了。注意此时不是只要只被怀疑过一遍的硬币就是假币。应该是被怀疑了最多次的硬币是假币。这在直观上很好理解:只有一枚假币,那“证据”最多的肯定“最假”。

python
# 刘家亦 24物院
n = int(input())
idx = {'up': 1, 'down': -1}
handl = {1:'heavy', 0:'light'}
for _ in range(n):
    suspect = [[0, 0, 0] for _ in range(12)]  # 0: light, 1: equal, 2: heavy
    times = [[0, 0] for _ in range(12)]
    for _ in range(3):
        left, right, res = input().split()
        if res == 'even':
            for coin in list(left + right):
                suspect[ord(coin) - ord('A')][1] += 1  # 标记为平衡
        else:
            for coin in list(left):suspect[ord(coin) - ord('A')][1 + idx[res]] += 1  # 根据天平状态标记轻或重
            for coin in list(right):suspect[ord(coin) - ord('A')][1 - idx[res]] += 1  # 相反方向标记
    for i in range(12):
        if suspect[i][1] == 0:
            if suspect[i][2] != 0 and suspect[i][0] == 0:
                times[i] = [suspect[i][2], 1]
            elif suspect[i][0] != 0 and suspect[i][2] == 0:
                times[i] = [suspect[i][0], 0]
    i = times.index(max(times))
    print(f'{chr(ord("A") + i)} is the counterfeit coin and it is {handl[times[i][1]]}.')