题目链接

小红玩纸牌

题目描述

给定一副数量庞大的扑克牌,包含四种花色('S', 'H', 'D', 'C')和不同的数值。小红每次可以从中取出 5 张牌,如果这 5 张牌构成“同花顺”,则获得 1 分,然后将这 5 张牌丢弃。

“同花顺”是指 5 张牌花色相同,且数值是连续的(例如:5H, 6H, 7H, 8H, 9H)。

任务是计算小红最多可以获得多少分。

解题思路

这个问题的核心在于,如何选择组成的同花顺,才能使得总的得分最高。因为取出的牌会被丢弃,所以一个选择可能会影响后续的选择。例如,一张 5H 既可以用于组成 1H-5H 的同花顺,也可以用于 2H-6H 等等。

由于不同花色的牌之间不能互相组成同花顺,我们可以将问题分解:分别计算每一种花色最多能获得多少分,然后将四个花色的得分相加,即为总分。

对于单一花色,我们拥有不同数值的牌以及它们各自的数量。问题转化为:给定一系列数值和对应的牌数,如何组成最多的 5 张连续数值的牌组?

这里可以采用一种贪心策略。我们按牌的数值从小到大进行遍历。对于当前我们拥有的数值为 的牌,我们贪心地检查是否能以它作为起始牌,形成一个 的同花顺。

能形成多少个这样的同花顺,取决于这个序列中最少的牌的数量。例如,如果我们有 2 张 ,3 张 ,1 张 ,5 张 和 4 张 ,那么我们最多只能形成 个以 开头的同花顺。

优化

一个朴素的实现是遍历从最小牌值到最大牌值的所有整数,但这在牌值范围很大但分布稀疏时会导致超时。正确的做法是只遍历我们实际拥有的牌的数值

贪心策略的正确性

我们从最小的数值开始,贪心地组成所有可能的同花顺。这样做是正确的,因为一个以较小数值 开头的同花顺 (v, ..., v+4) 所使用的牌,只会与以 开头的同花顺竞争资源。通过优先满足数值最小的组合,我们确保了这些“最紧急”的牌(因为它们无法参与更小数值的顺子)被优先使用,从而不会因为保留它们而错失当前的机会。这种局部最优的选择最终能导向全局最优。

算法步骤

  1. 数据存储:使用四个 map(或哈希表)来分别存储四种花色的牌,键是牌的数值,值是数量。

  2. 按花色计算:对每一种花色,执行以下操作:

    a. 获取该花色所有存在的牌的数值,并进行排序。

    b. 遍历这些排好序的数值

    c. 对于每个 ,检查其剩余数量。如果大于 0,则计算可以组成的以 为起点的同花顺数量 等于 这五张牌中数量的最小值。

    d. 如果 ,则将总分增加 ,并从 map 中将这五种牌的数量都减去

  3. 汇总:将四种花色的得分相加,得到最终答案。

代码

#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <algorithm>

using namespace std;

long long solve_suit(map<int, long long>& counts) {
    if (counts.empty()) {
        return 0;
    }
    long long score = 0;
    
    vector<int> keys;
    for (auto const& [key, val] : counts) {
        keys.push_back(key);
    }

    for (int v : keys) {
        if (counts.count(v) == 0 || counts[v] == 0) {
            continue;
        }

        long long num_flushes = counts[v];
        for (int i = 1; i < 5; ++i) {
            num_flushes = min(num_flushes, counts.count(v + i) ? counts[v + i] : 0);
        }

        if (num_flushes > 0) {
            score += num_flushes;
            for (int i = 0; i < 5; ++i) {
                counts[v + i] -= num_flushes;
            }
        }
    }
    return score;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    cin >> n;

    map<char, map<int, long long>> suits;
    for (int i = 0; i < n; ++i) {
        int value;
        long long count;
        char suit_char;
        cin >> value >> count >> suit_char;
        suits[suit_char][value] += count;
    }

    long long total_score = 0;
    total_score += solve_suit(suits['S']);
    total_score += solve_suit(suits['H']);
    total_score += solve_suit(suits['D']);
    total_score += solve_suit(suits['C']);

    cout << total_score << endl;

    return 0;
}
import java.util.Scanner;
import java.util.Map;
import java.util.TreeMap;
import java.util.Arrays;

public class Main {
    public static long solveSuit(TreeMap<Integer, Long> counts) {
        if (counts.isEmpty()) {
            return 0;
        }
        long score = 0;
        
        Integer[] keys = counts.keySet().toArray(new Integer[0]);

        for (int v : keys) {
            if (counts.getOrDefault(v, 0L) == 0) {
                continue;
            }

            long numFlushes = counts.get(v);
            for (int i = 1; i < 5; i++) {
                numFlushes = Math.min(numFlushes, counts.getOrDefault(v + i, 0L));
            }

            if (numFlushes > 0) {
                score += numFlushes;
                for (int i = 0; i < 5; i++) {
                    counts.put(v + i, counts.get(v + i) - numFlushes);
                }
            }
        }
        return score;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();

        Map<Character, TreeMap<Integer, Long>> suits = new TreeMap<>();
        suits.put('S', new TreeMap<>());
        suits.put('H', new TreeMap<>());
        suits.put('D', new TreeMap<>());
        suits.put('C', new TreeMap<>());

        for (int i = 0; i < n; i++) {
            int value = sc.nextInt();
            long count = sc.nextLong();
            char suitChar = sc.next().charAt(0);
            suits.get(suitChar).put(value, suits.get(suitChar).getOrDefault(value, 0L) + count);
        }

        long totalScore = 0;
        totalScore += solveSuit(suits.get('S'));
        totalScore += solveSuit(suits.get('H'));
        totalScore += solveSuit(suits.get('D'));
        totalScore += solveSuit(suits.get('C'));

        System.out.println(totalScore);
    }
}
import sys

def solve_suit(counts):
    if not counts:
        return 0
    
    score = 0
    for v in sorted(counts.keys()):
        if counts.get(v, 0) == 0:
            continue

        num_flushes = counts.get(v, 0)
        for i in range(1, 5):
            num_flushes = min(num_flushes, counts.get(v + i, 0))
        
        if num_flushes > 0:
            score += num_flushes
            for i in range(5):
                counts[v + i] -= num_flushes
    return score

def main():
    try:
        n_str = sys.stdin.readline()
        if not n_str: return
        n = int(n_str)
        suits = {'S': {}, 'H': {}, 'D': {}, 'C': {}}
        for _ in range(n):
            line = sys.stdin.readline()
            if not line: break
            value, count, suit_char = line.split()
            value, count = int(value), int(count)
            
            current_counts = suits[suit_char]
            current_counts[value] = current_counts.get(value, 0) + count

        total_score = 0
        total_score += solve_suit(suits['S'])
        total_score += solve_suit(suits['H'])
        total_score += solve_suit(suits['D'])
        total_score += solve_suit(suits['C'])
        
        print(total_score)
    except (IOError, ValueError):
        pass

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:贪心算法
  • 时间复杂度,其中 是输入的牌种类数。主要开销来自于将所有牌读入并存入 map 中(每次插入为 ,其中 是 map 中的元素数量),以及后续遍历每个花色的牌(每个花色有 种牌,遍历一遍为 )。
  • 空间复杂度,用于存储所有牌的信息。