CF 260B - Ancient Prophesy
The input is a long string consisting only of digits and the character -. Somewhere inside this string there may be many substrings that look like dates written exactly as dd-mm-yyyy. Our task is to find the valid date that appears most often as such a substring.
Rating: 1600
Tags: brute force, implementation, strings
Solve time: 2m 10s
Verified: yes
Solution
Problem Understanding
The input is a long string consisting only of digits and the character -. Somewhere inside this string there may be many substrings that look like dates written exactly as dd-mm-yyyy.
Our task is to find the valid date that appears most often as such a substring. A valid date must satisfy three conditions:
First, the format must be exactly two digits for the day, two digits for the month, and four digits for the year, separated by hyphens.
Second, the year must be one of 2013, 2014, or 2015.
Third, the day must be valid for the given month. Since none of these years are leap years, February has 28 days.
The answer is guaranteed to exist and to be unique.
The string length is at most 10^5. A substring representing a date always has length 10, so there are at most about 10^5 candidate positions to examine. This immediately suggests that scanning all possible length-10 substrings is feasible. Any solution that tries to compare every candidate against every other candidate would drift toward quadratic behavior and become too slow.
The subtle part is that a substring may look like a date syntactically while being invalid. For example:
31-02-2013
This matches the pattern dd-mm-yyyy, but February never has 31 days, so it must not be counted.
Another easy mistake is accepting dates that do not use exactly two digits for day and month. Consider:
1-01-2013
This is not a valid occurrence because the required format is exactly ten characters long. Only:
01-01-2013
counts.
A third pitfall is month-specific day limits. For example:
31-04-2014
April has only 30 days. A solution that merely checks 1 <= day <= 31 would incorrectly count it.
Finally, overlapping occurrences are allowed. In the string
0012-10-2012-10-2012
different starting positions can produce valid date substrings. We must examine every length-10 window independently.
Approaches
A brute-force idea is to generate every valid date between 2013 and 2015 and search the entire prophecy for each one.
There are only about a thousand valid dates in that range, so correctness is straightforward. For each date, count how many times its string representation appears in the prophecy and keep the maximum.
The problem is the running time. The prophecy can contain 10^5 characters. Searching for roughly 1000 dates independently leads to around 10^8 character operations in the worst case, which is unnecessarily expensive.
The structure of the problem suggests turning the process around.
Instead of asking, "How many times does each valid date occur?", we can ask, "What date does each length-10 substring represent?"
Every valid occurrence must occupy exactly ten characters:
dd-mm-yyyy
There are only n - 9 such windows in the entire string. For each window we can check whether it forms a valid date. If it does, we increment that date's frequency.
This transforms the problem into a single linear scan of the prophecy. Each position is processed once, validity checks take constant time, and a hash map stores frequencies.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(1000·n) | O(1) | Too slow |
| Optimal | O(n) | O(k) | Accepted |
Here k is the number of distinct valid dates encountered, which is at most the number of valid dates between 2013 and 2015.
Algorithm Walkthrough
- Read the prophecy string.
- Prepare an array containing the number of days in each month:
[31,28,31,30,31,30,31,31,30,31,30,31]
Since years 2013 through 2015 are not leap years, February always has 28 days.
3. Iterate over every starting position i such that a length-10 substring exists.
4. Extract the substring s[i:i+10].
5. Check whether positions 2 and 5 contain hyphens.
If they do not, the substring cannot match the required format. 6. Parse:
- day from characters
[0:2] - month from characters
[3:5] - year from characters
[6:10]
If parsing fails, discard the substring. 7. Verify the date:
- year is between 2013 and 2015
- month is between 1 and 12
- day is at least 1
- day does not exceed the number of days in that month
- If the date is valid, increment its count in a hash map using the original substring as the key.
- Track the date with the highest frequency seen so far.
- After processing all windows, output the date with the maximum count.
Why it works
Every valid occurrence in the prophecy occupies exactly ten consecutive characters and must be represented by some length-10 window. The algorithm examines every such window exactly once.
A substring contributes to the frequency map only if it passes all validity checks, so every counted occurrence corresponds to a correct date. Conversely, every correct date occurrence appears as one of the examined windows and will be counted.
The frequency map therefore stores the exact number of occurrences of every valid date. Since the apocalypse date is defined as the unique valid date with maximum frequency, selecting the key with the largest count produces the correct answer.
Python Solution
import sys
input = sys.stdin.readline
def solve():
s = input().strip()
days_in_month = [31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31]
freq = {}
best_date = ""
best_count = 0
for i in range(len(s) - 9):
cur = s[i:i + 10]
if cur[2] != '-' or cur[5] != '-':
continue
try:
day = int(cur[0:2])
month = int(cur[3:5])
year = int(cur[6:10])
except ValueError:
continue
if not (2013 <= year <= 2015):
continue
if not (1 <= month <= 12):
continue
if not (1 <= day <= days_in_month[month - 1]):
continue
freq[cur] = freq.get(cur, 0) + 1
if freq[cur] > best_count:
best_count = freq[cur]
best_date = cur
print(best_date)
if __name__ == "__main__":
solve()
The scan examines every possible length-10 window because any valid date occurrence must occupy exactly ten characters.
The hyphen check is performed before parsing integers. This quickly rejects most invalid windows and avoids unnecessary conversions.
The month validity check comes before indexing days_in_month[month - 1]. Reversing this order would risk accessing an invalid index when the month is outside the range 1..12.
The original substring is used as the dictionary key. Since dates are always stored in canonical dd-mm-yyyy format, identical dates always produce identical keys.
The running maximum is updated during the scan, avoiding a second pass over the frequency map.
Worked Examples
Example 1
Input:
777-444---21-12-2013-12-2013-12-2013---444-777
Relevant valid windows:
| Window | Valid? | Count After Update |
|---|---|---|
| 21-12-2013 | Yes | 1 |
| 13-12-2013 | Yes | 1 |
| 12-20-1312 | No | - |
| 13-12-2013 | Yes | 2 |
| 13-12-2013 | Yes | 3 |
Final frequencies:
| Date | Frequency |
|---|---|
| 21-12-2013 | 1 |
| 13-12-2013 | 3 |
Output:
13-12-2013
This example shows why every length-10 window must be checked. The winning date appears multiple times due to overlapping positions inside the larger string.
Example 2
Input:
01-01-201331-02-201301-01-2013
Trace:
| Window | Valid? | Frequency |
|---|---|---|
| 01-01-2013 | Yes | 1 |
| 31-02-2013 | No | - |
| 01-01-2013 | Yes | 2 |
Output:
01-01-2013
This example demonstrates the importance of validating month-specific day limits. The middle substring has the correct shape but is not a real calendar date.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each length-10 window is processed once |
| Space | O(k) | Frequency map stores distinct valid dates |
The string length is at most 10^5, so roughly 10^5 windows are examined. Each window requires only constant-time parsing and validation. The solution comfortably fits within the time limit and uses very little memory.
Test Cases
# helper: run solution on input string, return output string
import sys
import io
def run(inp: str) -> str:
old_stdin = sys.stdin
old_stdout = sys.stdout
sys.stdin = io.StringIO(inp)
out = io.StringIO()
sys.stdout = out
def solve():
input = sys.stdin.readline
s = input().strip()
days = [31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31]
freq = {}
best_date = ""
best_count = 0
for i in range(len(s) - 9):
cur = s[i:i + 10]
if cur[2] != '-' or cur[5] != '-':
continue
try:
d = int(cur[:2])
m = int(cur[3:5])
y = int(cur[6:])
except ValueError:
continue
if not (2013 <= y <= 2015):
continue
if not (1 <= m <= 12):
continue
if not (1 <= d <= days[m - 1]):
continue
freq[cur] = freq.get(cur, 0) + 1
if freq[cur] > best_count:
best_count = freq[cur]
best_date = cur
print(best_date)
solve()
sys.stdin = old_stdin
sys.stdout = old_stdout
return out.getvalue().strip()
# provided sample
assert run(
"777-444---21-12-2013-12-2013-12-2013---444-777\n"
) == "13-12-2013", "sample"
# single valid date
assert run("01-01-2013\n") == "01-01-2013", "minimum valid case"
# invalid date between two valid occurrences
assert run(
"01-01-201331-02-201301-01-2013\n"
) == "01-01-2013", "reject invalid February date"
# month boundary
assert run(
"30-04-201431-04-201430-04-2014\n"
) == "30-04-2014", "April has only 30 days"
# year boundary
assert run(
"31-12-201231-12-201331-12-2013\n"
) == "31-12-2013", "reject year outside range"
| Test input | Expected output | What it validates |
|---|---|---|
01-01-2013 |
01-01-2013 |
Smallest meaningful valid input |
01-01-201331-02-201301-01-2013 |
01-01-2013 |
Invalid February date is ignored |
30-04-201431-04-201430-04-2014 |
30-04-2014 |
Month-specific day limits |
31-12-201231-12-201331-12-2013 |
31-12-2013 |
Year range validation |
Edge Cases
Consider:
31-02-2013
The substring has the correct dd-mm-yyyy structure. The algorithm parses day 31, month 2, and year 2013. The month is valid, but February allows only 28 days. The condition
day <= days_in_month[month - 1]
fails, so the substring is discarded and never counted.
Consider:
31-04-2014
April has 30 days. The algorithm computes days_in_month[3] = 30, detects that 31 > 30, and rejects the date. This prevents counting syntactically correct but impossible calendar dates.
Consider:
1-01-2013
This string is only nine characters long. Since the algorithm only examines length-10 windows, it never treats this as a candidate date. The required format demands exactly two digits for both day and month.
Consider:
13-12-2013-12-2013
The substring 13-12-2013 appears at the beginning, and another occurrence begins later due to overlap. Because the algorithm checks every starting position independently, both occurrences are counted correctly. Overlapping matches require no special handling.