Editorial for Name Tag Tally


Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.
Submitting an official solution before solving the problem yourself is a bannable offence.

Author: ray

There's quite a bit to process with this question, so let's break it down.

You play a game where you select name tags from a bag, with each name tag containing a name consisting of three lowercase English letters. The name on the name tag will be used to calculate your score for that a round. There are n rounds to play, where in each round, you are given three variables a, b, and c, which are also used to calculate the final score.

Based on the rules that are given to you concerning these variables, you want to calculate what your score would be on average, when considering all possible permutations of of the characters in the name.

The Naive Approach

The naive (or basic) approach would be to generate all possible permutations and find the score for each of them. Since there are 26 letters in the English alphabet and three letters in the names, we would be working with 26^3 possibilities.

We can keep a running total for the score, and then for each possible name, add to the score:

  • for each character in the name, if the character is a vowel, add a to the score;
  • for each character in the name, if the index of the character within the alphabet (e.g., 'a' = 0, 'b' = 1, etc.) is divisible by 3, then add b to the score; and
  • if the name starts with the first five letters of the alphabet (e.g., 'a', 'b', 'c', 'd', 'e'), then add c to the score.

Then, since we want to find the average score over all possibilities, we divide the total score by 26^3 to get our final result for that round.

For each round, we have three loops, making the algorithm O(M^k) where M is the size of our alphabet (in this case, it's 26) and k is the length of the name (which is 3). Given we have n rounds of games, we would end up with an O(nM^k) algorithm!

While this method was allowed during the contest, it is not a particularly efficient one, due to generating all permutations of the name and calculating the score for every possibility.

The Efficient Approach

By doing a bit of maths, we can turn the O(nM^k) algorithm to just O(n). Wow!

When assigning points for a, we observe that there are only 5 vowels out of the 26 possible letters in the alphabet for each of the 3 characters in the name. And as we saw before, there are 26^3 permutations. As such, the total (unadjusted) score when considering a is:

\displaystyle \text{score}_a = a \times \frac{5}{26}\times 3 \times 26^3 = a \times 5 \times 3 \times 26^2 = 10140a

Next, when assigning points for b, we consider the fact that every third letter out of the 26 letters we have in the alphabet is valid. Consequently, for each name, a third of the names are valid. This means we can take total permutations and divide it by 3:

\displaystyle \text{score}_b = \frac{26^3}{3}b

Then, when assigning points for c, only the first character in the name can be the first 5 letters in the alphabet. This means that there are 5 \times 26 \times 26 possible names that follow the rule. As such, the total unadjusted score when considering c is:

\displaystyle \text{score}_c = c \times 5 \times 26 \times 26 = 3380c

Since the scores we've just obtained are total scores, we need to average them out by dividing by 26^3:

\displaystyle \text{score} = \frac{\text{score}_a + \text{score}_b + \text{score}_c}{26^3}

Since these arithmetic operations are constant time, for n rounds, we end up with a time complexity of O(n) — a big improvement over the naive implementation. These solutions can be O(1) space additionally, as we can process and output while taking the input of a,b,c.

Naive C++ solution:

#include <bits/stdc++.h>
using namespace std;

bool is_vowel(int x) {
    return x == 'a'-'a' || x == 'e'-'a' || x == 'i'-'a' || x == 'o'-'a' || x == 'u'-'a';
}

int main() {
    int n;
    cin >> n;

    while (n--) {
        int a,b,c;
        cin >> a >> b >> c;

        long long score = 0;
        for (int i = 0; i < 26; i++) {
            for (int j = 0; j < 26; j++) {
                for (int k = 0; k < 26; k++) {
                    score += a*(is_vowel(i)+is_vowel(j)+is_vowel(k));
                    score += b*((i+j+k)%3 == 0);
                    score += c*(i < 5);
                }
            }
        }
        cout << fixed << setprecision(2) << (double)score/(26.0*26.0*26.0) << endl;
    }
}

Efficient C++ solution:

#include <bits/stdc++.h>
using namespace std;

const int as = 3*5*26*26;
const int bs = 26*26*26/3;
const int cs = 5*26*26;

int main() {
    int n;
    cin >> n;

    int a,b,c;
    while (n--) {
        cin >> a >> b >> c;
        cout << fixed << setprecision(2) << (double)(a*as + b*bs + c*cs)/(26.0*26.0*26.0) << endl;
    }
}

Python solution: Please excuse my Pythonic style

n = int(input())

AS = 3 * 5 * 26 * 26
BS = 26 * 26 * 26 // 3
CS = 5 * 26 * 26

print(
    *[
        "{:.2f}".format((a * AS + b * BS + c * CS) / (26.0 * 26.0 * 26.0))
        for a, b, c in [map(int, input().split()) for _ in range(n)]
    ],
    sep="\n",
)

Comments

There are no comments at the moment.