#![warn(clippy::pedantic)]

use std::{cmp::Ordering, io::stdin};

use itertools::Itertools;
use strum::{EnumIter, IntoEnumIterator};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
enum Card {
    N2,
    N3,
    N4,
    N5,
    N6,
    N7,
    N8,
    N9,
    T,
    J,
    Q,
    K,
    A,
}

impl Card {
    fn parse(c: char) -> Self {
        match c {
            '2' => Card::N2,
            '3' => Card::N3,
            '4' => Card::N4,
            '5' => Card::N5,
            '6' => Card::N6,
            '7' => Card::N7,
            '8' => Card::N8,
            '9' => Card::N9,
            'T' => Card::T,
            'J' => Card::J,
            'Q' => Card::Q,
            'K' => Card::K,
            'A' => Card::A,
            _ => unreachable!(),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Type {
    HighCard,
    OnePair,
    TwoPairs,
    ThreeSame,
    FullHouse,
    FourSame,
    FiveSame,
}

impl Type {
    fn classify_inner(mut value: [Card; 5]) -> Option<Self> {
        value.sort();
        let groups: Vec<_> = value
            .into_iter()
            .group_by(|c| *c)
            .into_iter()
            .map(|(_card, group)| group.count())
            .collect();

        if groups.contains(&5) {
            Some(Type::FiveSame)
        } else if groups.contains(&4) {
            Some(Type::FourSame)
        } else if groups.contains(&3) {
            if groups.contains(&2) {
                Some(Type::FullHouse)
            } else {
                Some(Type::ThreeSame)
            }
        } else if groups.contains(&2) {
            if groups.iter().filter(|&&n| n == 2).count() == 2 {
                Some(Type::TwoPairs)
            } else {
                Some(Type::OnePair)
            }
        } else if groups.iter().all(|&n| n == 1) {
            Some(Type::HighCard)
        } else {
            None
        }
    }

    fn classify(value: [Card; 5], mode: Mode) -> Option<Self> {
        match mode {
            Mode::Jokers => {
                if !value.contains(&Card::J) {
                    return Self::classify_inner(value);
                }

                let jokers: Vec<_> = value.iter().positions(|&c| c == Card::J).collect();

                let all_other_cards = Card::iter().filter(|&c| c != Card::J);
                std::iter::repeat(all_other_cards)
                    .take(jokers.len())
                    .multi_cartesian_product()
                    .map(|replacements| {
                        let mut value = value;
                        for (&pos, replacement) in jokers.iter().zip(replacements) {
                            value[pos] = replacement;
                        }
                        value
                    })
                    .map(Type::classify_inner)
                    .max()
                    .unwrap()
            }
            Mode::NoJokers => Self::classify_inner(value),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
    Jokers,
    NoJokers,
}

#[derive(Debug, Clone, Copy)]
struct Hand {
    typ: Option<Type>,
    cards: [Card; 5],

    mode: Mode,
    bid: usize,
}

impl Hand {
    pub fn parse(s: &str, mode: Mode) -> Self {
        let (cards, bid) = s.split_once(' ').unwrap();
        let bid = bid.parse().unwrap();
        let cards: Vec<_> = cards.chars().map(Card::parse).collect();
        let cards = *<&[Card; 5]>::try_from(cards.as_slice()).unwrap();
        let typ = Type::classify(cards, mode);

        Self {
            typ,
            cards,
            mode,
            bid,
        }
    }

    pub fn set_mode(&mut self, mode: Mode) {
        if mode == self.mode {
            return;
        }
        self.typ = Type::classify(self.cards, mode);
        self.mode = mode;
    }
}

impl PartialEq for Hand {
    fn eq(&self, other: &Self) -> bool {
        assert!(
            self.mode == other.mode,
            "can't compare hands with different modes"
        );
        self.cards == other.cards
    }
}

impl Eq for Hand {}

impl Ord for Hand {
    fn cmp(&self, other: &Self) -> Ordering {
        assert!(
            self.mode == other.mode,
            "can't compare hands with different modes"
        );

        self.typ.cmp(&other.typ).then_with(|| {
            self.cards
                .iter()
                .zip(&other.cards)
                .map(|(s, o)| {
                    if let Mode::Jokers = self.mode {
                        match (s, o) {
                            (Card::J, Card::J) => {}
                            (Card::J, _) => return Ordering::Less,
                            (_, Card::J) => return Ordering::Greater,
                            _ => {}
                        }
                    }

                    s.cmp(o)
                })
                .reduce(Ordering::then)
                .unwrap()
        })
    }
}

impl PartialOrd for Hand {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

fn get_winnings(hands: &mut [Hand], mode: Mode) -> usize {
    for hand in hands.iter_mut() {
        hand.set_mode(mode);
    }
    hands.sort();
    hands
        .iter()
        .enumerate()
        .map(|(rank, hand)| hand.bid * (rank + 1))
        .sum()
}

fn main() {
    let mut hands: Vec<_> = stdin()
        .lines()
        .map(Result::unwrap)
        .map(|s| Hand::parse(&s, Mode::NoJokers))
        .collect();

    println!("{}", get_winnings(&mut hands, Mode::NoJokers));
    println!("{}", get_winnings(&mut hands, Mode::Jokers));
}
