找回密码
 立即注册
搜索
热搜: 活动 交友
查看: 34|回复: 5

【acsl全明星备赛】发个真题

[复制链接]

12

主题

39

回帖

981

积分

资深程序员

积分
981
发表于 昨天 17:02 | 显示全部楼层 |阅读模式
本帖最后由 脆脆大奶酪 于 8-13-2025 22:18 编辑

问题: Thirteen 是一种流行的多人纸牌游戏。该游戏采用多副纸牌,每副纸牌有 52 张牌,包括四种花色:Clubs(梅花)、Diamonds(方块)、Hearts(红心)和 Spades(黑桃),牌面从 2 到 A。在这个游戏中,牌的花色不作区分,且 2 是最大的牌。因此,所有牌按牌面从小到大排列为:3,4,5,…,9,T ( 10 ),J,Q,K,A,2。

游戏按轮次进行。每位玩家的目标是在遵守以下规则的前提下,出完手中所有牌。游戏从 1 号玩家开始,依次是 2 号、3 号玩家 ......。所有玩家依次出牌后又回到 1 号玩家继续出牌。每轮游戏的具体规则如下:


● 每轮游戏的首位玩家需确定她手中每种牌面的最大张数 X,写作 “X-of-a-kind”,即由 X 张相同牌面的牌组成。例如,2-of-a-kind (即 2 张相同牌面的牌)、3-of-a-kind (即 3 张相同牌面的牌)。在这些 X-of-a-kind 中,首位玩家打出她手中所有牌面最小的牌。

● 之后的玩家只能在拥有相同数量但牌面更大的牌组时才能出牌,否则此轮不能出牌。当没有玩家能够打出相同数量但牌面更大的牌组时,这一轮游戏结束。

下一轮从上一轮最后出牌的玩家开始。如果该玩家此时已经没有牌了,那么下一轮就由该玩家后面的玩家开始。若除某一位玩家外,其他所有玩家均已出完手中的牌,则游戏结束。

你将得到一个整数 N,代表玩家人数;然后得到 N 个字符串,每个字符串包含一位玩家的初始牌,每张牌之间用一个空格隔开。N 的值介于 3 和 10 之间(包括 3 和 10)。最后以单个字符串的形式,按照玩家出完牌的先后顺序(未出完牌的玩家排在最后)输出玩家编号,每个编号之间用一个空格隔开。




输入: 一个正整数 N,后面跟着 N 个字符串。每个字符串包含游戏开始时每位玩家手中的牌。其中每张牌用两个字符表示,这两个字符分别为牌面大小(3、4、5、6、7、8、9、T、J、Q、K、A、2)和花色(C、D、H、S),且每张牌之间用一个空格隔开。每轮游戏玩家不超过 10 人,每人手中的牌不超过 100 张。

输出: 按照玩家完成游戏的顺序输出玩家编号,包括最后一名未完成游戏的玩家,每位玩家之间用一个空格隔开。除一名玩家外,所有玩家都肯定会完成游戏,即出完手中所有的牌。


样例输入样例输出
3JC 6C QD AH 8H TS 5D JD 7D 8D 9C 6H QS 9H 4H KH KS9D 7S JS 2S 8C TC JH 7C TD 4D 6D 4C 5S 3H 2D 3C 6SQC 2H AC KC 5H TH KD AD AS 4S 3S 9S 8S 5C 2C QH 3D3 1 2
46S 9C 9H QC 9S 5H KS TC 5C QH JS 5D JH3C 7S 5S JD 8H QD TD 2C 6D TS 2D 3S 8DTH AC 7H 6H 4D 4H 3D AD 7D 2H 8S 6C 4SKC 2S 9D AS 7C KD KH 3H JC 4C QS 8C AH1 2 3 4
56C 8H 7C 5H 6H AD TC AC TD JH7D KD 4C 5D 9H 8D 9D KH 8C KC3D 5S QD JC 7S 4H JS 3S 8S 3CJD 7H 2C 4D AH TS 2D 6D AS 3HQC KS 2H 5C 6S QS TH 9C 2S 4S1 5 2 3 4


本题肉眼可见没什么高深的算法,是一道“模拟”题,我们要做的只是正确存放读入的手牌数据,然后按照出牌规则进行游戏,答案会在过程里自然得出。所以重点在于理解规则:
首先手牌的花色是无效信息,只有点数有意义,所以在读取时可以直接按照点数进行排序,
其次是相同点数的张数和点数大小,决定了出牌顺序,所以我们在读入数据时同时记录每个点数的张数可以更方便后续出牌,


cards = "3456789TJQKA2"

def count_cards(pStr):#处理输入数据,得到对应点数的牌都有几张
    numStr = "0000000000000"
    for i in pStr:
        numStr = numStr[:cards.find(i)] + str(eval(numStr[cards.find(i)]) + 1) + numStr[(cards.find(i) + 1):]
    return numStr

n = eval(input())
total = []
pCount = []
for i in range(n):
    nStr = ""
    kList = list(input().split())#得到每张牌
    for j in kList:
        nStr += j[:1]#去掉花色
    total.append(nStr)#存为初始手牌
    numStr = count_cards(nStr)#计算张数
    pCount.append(numStr)

本文的程序是取自某同学比赛时的满分程序,因为acsl不开放赛后继续提交,所以只能使用参赛程序以确保正确性
上面是读入数据和处理,每个人的手牌存为13位字符串,对应的数字为张数,
然后就是出牌逻辑:
首轮由一号先出,后面由上一轮最后出牌的玩家先出,如果该玩家已经出完牌了则顺延;
先出的玩家出牌规则是先看同点数张数最多的,张数相同再看点数最低的;
后面的玩家则必须出和上家相同张数且点数更大的,有多组满足要求则出点数最小的





12

主题

39

回帖

981

积分

资深程序员

积分
981
 楼主| 发表于 昨天 17:03 | 显示全部楼层
怎么后面都被吞了

12

主题

39

回帖

981

积分

资深程序员

积分
981
 楼主| 发表于 昨天 17:04 | 显示全部楼层

12

主题

39

回帖

981

积分

资深程序员

积分
981
 楼主| 发表于 昨天 17:12 | 显示全部楼层
晚上再重新码一下

12

主题

39

回帖

981

积分

资深程序员

积分
981
 楼主| 发表于 昨天 22:04 | 显示全部楼层
def find_max(nStr):#每轮首次出牌
    maxCnt, maxInd = 0, 0
    for i in range(len(nStr)):
        if eval(nStr) > maxCnt:
            maxCnt = eval(nStr)
            maxInd = i
    return maxCnt, maxInd


def find_deal(nStr, nowLen, cardId):#接牌
    for i in range(len(nStr)):
        if eval(nStr) == nowLen:
            if i > cardId:
                return i
    return -1

pList = []#出完牌的列表
turn = 0#当前出牌玩家
nowLen = 0#当前出牌张数(0代表本轮首次出牌)
bidder = 0#上一个出牌的玩家
cardId = 0#上一个出牌的点数
times = 0#仔细读了之后感觉是个flag
while len(pList) < (n - 1):#全部出完牌后退出循环
    if turn == bidder:#上一次出牌后无人接牌
        times += 1#标记一下
    if (turn + 1) in pList:#当前玩家已经出完了
        turn = (turn + 1) % n#顺延下一个玩家
        continue
    print(pCount[turn])
    print(cards[cardId], nowLen)
    if nowLen == 0:#如果是本轮首次出牌
        times = 0
        nowLen, cardId = find_max(pCount[turn])#确定出牌的张数和点数
        #更新出牌后的数据
        bidder = turn
        playerStr = pCount[turn]
        playerStr = playerStr[:cardId] + str(eval(playerStr[cardId]) - nowLen) + playerStr[(cardId + 1):]
        pCount[turn] = playerStr
        if playerStr == "0000000000000":#当前玩家正好把牌出完了
            pList.append(turn + 1)#记录
        turn = (turn + 1) % n#轮到下一位玩家
        continue
    else:#接牌
        if turn == bidder:#接自己的牌
            times = 0
            newId = find_deal(pCount[turn], nowLen, cardId)#检测能否接牌
            if newId == -1:#接不了
                nowLen = 0
                continue
            #能接,出牌并更新数据
            cardId = newId
            playerStr = pCount[turn]
            playerStr = playerStr[:cardId] + str(eval(playerStr[cardId]) - nowLen) + playerStr[(cardId + 1):]
            pCount[turn] = playerStr
            if playerStpList = []#出完牌的列表
turn = 0#当前出牌玩家
nowLen = 0#当前出牌张数(0代表本轮首次出牌)
bidder = 0#上一个出牌的玩家
cardId = 0#上一个出牌的点数
times = 0#仔细读了之后感觉是个flag
while len(pList) < (n - 1):#全部出完牌后退出循环
    if turn == bidder:#上一次出牌后无人接牌
        times += 1#标记一下
    if (turn + 1) in pList:#当前玩家已经出完了
        turn = (turn + 1) % n#顺延下一个玩家
        continue
    print(pCount[turn])
    print(cards[cardId], nowLen)
    if nowLen == 0:#如果是本轮首次出牌
        times = 0
        nowLen, cardId = find_max(pCount[turn])#确定出牌的张数和点数
        #更新出牌后的数据
        bidder = turn
        playerStr = pCount[turn]
        playerStr = playerStr[:cardId] + str(eval(playerStr[cardId]) - nowLen) + playerStr[(cardId + 1):]
        pCount[turn] = playerStr
        if playerStr == "0000000000000":#当前玩家正好把牌出完了
            pList.append(turn + 1)#记录
        turn = (turn + 1) % n#轮到下一位玩家
        continue
    else:#接牌
        if turn == bidder:#接自己的牌
            times = 0r == "0000000000000":
                pList.append(turn + 1)
            turn = (turn + 1) % n
            continue
        elif times != 0:#上一次出牌后别人接不了,自己也接不了,此轮结束
            bidder = turn#最后一次出牌的玩家先出
            times = 0
            continue
        else:#接别人的牌
            newId = find_deal(pCount[turn], nowLen, cardId)
            if newId == -1:
                turn = (turn + 1) % n
                continue
            times = 0
            cardId = newId
            bidder = turn
            playerStr = pCount[turn]
            playerStr = playerStr[:cardId] + str(eval(playerStr[cardId]) - nowLen) + playerStr[(cardId + 1):]
            pCount[turn] = playerStr
            if playerStr == "0000000000000":
                pList.append(turn + 1)
            turn = (turn + 1) % n
            continue

#输出结果
nSum = round(n * (n + 1) / 2)
for k in pList:
    nSum -= k
roadStr = ""
for k in pList:
    roadStr += str(k)
    roadStr += " "
roadStr += str(nSum)
print(roadStr)
剩下就是工程问题,在脑子里或者纸上模拟出牌过程然后翻译到程序里,每个人都会有不同的处理方式,重点注意边界情况和题目限制即可。自行给程序添加了大部分注释,如果还有不理解的地方请务必留言,这些地方才是备赛最大的意义所在。

12

主题

39

回帖

981

积分

资深程序员

积分
981
 楼主| 发表于 昨天 22:07 | 显示全部楼层
脆脆大奶酪 发表于 8-13-2025 22:04
def find_max(nStr):#每轮首次出牌
    maxCnt, maxInd = 0, 0
    for i in range(len(nStr)):

之前可能是超出字符限制了,现在能看了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|RealDevClub ( 沪ICP备2024093864号-1 )

GMT+8, 8-14-2025 08:40 , Processed in 0.062424 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表