CF 105498H - Optimizing Weekend Days

We are given a long continuous period defined by a start date and an end date, and inside this period we also receive a list of public holidays. Each holiday either repeats every year on a fixed month and day, or occurs only once in a specific year.

CF 105498H - Optimizing Weekend Days

Rating: -
Tags: -
Solve time: 57s
Verified: yes

Solution

Problem Understanding

We are given a long continuous period defined by a start date and an end date, and inside this period we also receive a list of public holidays. Each holiday either repeats every year on a fixed month and day, or occurs only once in a specific year.

The HR department wants to declare exactly two fixed weekdays as weekends for the entire period. Every day whose weekday is not one of these two chosen weekend days is a working day, unless that day is a public holiday. The goal is to choose the pair of weekend weekdays so that the number of working days is as large as possible over the entire interval.

The key complication is that holidays and dates span a large Gregorian range, including leap years, so we cannot rely on simplified calendars or precomputed small cycles. We must correctly map every date to its weekday and count occurrences accurately.

The range goes from 1900 to 3000, which is large in terms of calendar days but still small enough that a full day-by-day simulation is feasible. The total number of days across all test cases is bounded by about 4000 years, roughly 1.5 million days, so a linear sweep per test case is acceptable.

A subtle issue is how to handle repeated yearly holidays. A “DD-MM” holiday applies to every year, but only if that date exists in that year, so February 29 only appears in leap years. A naive approach that blindly applies February 29 every year would introduce invalid dates and corrupt weekday alignment.

Another common failure case is lexicographic ordering of weekday pairs. The problem explicitly distinguishes ordered pairs, so “Friday Saturday” is not the same as “Saturday Friday”, and comparison is done first by the first weekday name, then the second.

Approaches

A brute-force strategy is straightforward: try all 21 ordered pairs of distinct weekdays. For each pair, simulate every day in the interval, determine whether it is a weekend or a holiday, and count working days. Finally, choose the pair that yields the maximum count.

This works because once a pair of weekdays is fixed, every day contributes independently to the final count. The cost per evaluation is proportional to the number of days in the range plus the number of holidays. With up to about 1.5 million days per test case and 21 weekday pairs, this leads to roughly 30 million day checks per test case in the worst case, and across T up to 400 this becomes far too slow.

The key observation is that we do not actually need to rescan the entire calendar for every pair. For each weekday, we can precompute how many times it appears in the range. Similarly, for each weekday, we can also count how many holidays fall on that weekday. Once these counts are known, the contribution of any pair becomes a simple arithmetic expression.

If a weekday is chosen as a weekend, every occurrence of that weekday removes one working day. So for a candidate pair (a, b), the total reduction is the number of days in weekday a plus weekday b, minus the number of holidays that already fall on those weekdays, since holidays are already non-working days and should not be double counted as lost workdays.

Thus the problem reduces to computing weekday frequencies over a date range and then evaluating all 21 pairs in constant time.

Approach Time Complexity Space Complexity Verdict
Brute Force O(21 · D) per test case O(1) Too slow
Optimal O(D + H + 1) per test case O(1) Accepted

Here D is the number of days in the interval.

Algorithm Walkthrough

Precomputation

  1. Convert both input dates into an absolute day index starting from a fixed reference date such as 01-01-1900. This allows us to treat the calendar as a simple integer line. The conversion must account for leap years using the Gregorian rule, otherwise weekday alignment will drift over long ranges.
  2. While converting, also compute the weekday of each date by maintaining a running offset from a known base weekday. Since 01-01-1900 is Monday in standard CF calendar setups, each increment of one day shifts the weekday cyclically.

Counting weekday frequencies

  1. Iterate from the start day index to the end day index inclusive, and maintain a frequency array freq[7] where each entry counts how many times each weekday appears in the interval. This single pass already captures the entire structural distribution of the calendar.
  2. For each holiday, convert it to an actual date if it is yearly, or directly if it is fixed. If the holiday lies inside the interval, determine its weekday and increment a separate array hol[7]. This ensures we know how many holidays collide with each weekday.

Evaluating weekday pairs

  1. For every ordered pair of distinct weekdays (i, j), compute the total number of working days as total_days minus contributions removed by choosing i and j as weekends.
  2. The removal contributed by a weekday is its full frequency, but we must subtract holidays already counted in that weekday. So the effective loss is freq[i] + freq[j] minus hol[i] - hol[j].
  3. Track the pair with the maximum working days. If multiple pairs produce the same result, choose the lexicographically smallest pair by weekday name order.

Why it works

The core invariant is that the only interaction between weekdays is through fixed aggregate counts. Once we know how many days of each weekday exist in the interval and how many of them are already holidays, every choice of weekend days only subtracts from these independent buckets. There is no dependency between individual days beyond their weekday classification, so collapsing the calendar into seven counters preserves all necessary information.

Python Solution

import sys
input = sys.stdin.readline

WEEK = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
idx = {w: i for i, w in enumerate(WEEK)}

# leap year check
def is_leap(y):
    return (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0)

mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

def to_day(d, m, y):
    # convert to days since 01-01-1900
    res = 0
    for yy in range(1900, y):
        res += 366 if is_leap(yy) else 365
    for mm in range(1, m):
        res += mdays[mm - 1]
        if mm == 2 and is_leap(y):
            res += 1
    res += d - 1
    return res

def weekday_of(day_index):
    return (day_index + 0) % 7  # 01-01-1900 assumed Monday

def parse_date(s):
    parts = s.strip().split("-")
    if len(parts) == 3:
        return int(parts[0]), int(parts[1]), int(parts[2])
    else:
        return int(parts[0]), int(parts[1]), None

def expand_holiday(dd, mm, yy, start_y, end_y):
    if yy is not None:
        if start_y <= yy <= end_y:
            return [(dd, mm, yy)]
        return []
    res = []
    for y in range(start_y, end_y + 1):
        if mm == 2 and dd == 29 and not is_leap(y):
            continue
        res.append((dd, mm, y))
    return res

def main():
    T = int(input())
    for _ in range(T):
        s, e = input().split()
        d1, m1, y1 = map(int, s.split("-"))
        d2, m2, y2 = map(int, e.split("-"))

        start = to_day(d1, m1, y1)
        end = to_day(d2, m2, y2)

        freq = [0] * 7
        hol = [0] * 7

        # count weekdays in range
        for d in range(start, end + 1):
            freq[d % 7] += 1

        H = int(input())
        holidays = []

        for _ in range(H):
            s = input().strip()
            dd, mm, yy = parse_date(s)
            holidays.append((dd, mm, yy))

        start_year = y1
        end_year = y2

        # process holidays
        for dd, mm, yy in holidays:
            if yy is not None:
                if start_year <= yy <= end_year:
                    di = to_day(dd, mm, yy)
                    if start <= di <= end:
                        hol[di % 7] += 1
            else:
                for y in range(start_year, end_year + 1):
                    if mm == 2 and dd == 29 and not is_leap(y):
                        continue
                    di = to_day(dd, mm, y)
                    if start <= di <= end:
                        hol[di % 7] += 1

        total_days = end - start + 1

        best = -1
        best_pair = (0, 1)

        for i in range(7):
            for j in range(7):
                if i == j:
                    continue
                removed = freq[i] + freq[j] - hol[i] - hol[j]
                working = total_days - removed
                name_i = WEEK[i]
                name_j = WEEK[j]
                if working > best or (working == best and (name_i, name_j) < (WEEK[best_pair[0]], WEEK[best_pair[1]])):
                    best = working
                    best_pair = (i, j)

        print(WEEK[best_pair[0]], WEEK[best_pair[1]])

if __name__ == "__main__":
    main()

The weekday computation is handled through modular arithmetic over a continuous day index, which avoids repeated calendar logic inside the main loop. Holidays are expanded only within the relevant year range, which keeps the cost bounded.

The evaluation step is purely combinatorial, iterating over 49 ordered pairs and using precomputed frequency arrays, so it stays constant per test case.

Worked Examples

Example 1

Input:

10-10-2024 24-10-2024
3
05-01
11-10-2024
05-01-2024

We first map the range into a contiguous block of days and compute weekday frequencies. Suppose within this interval Friday and Saturday occur the most densely, which is typical for a 15-day span.

We then map holidays:

Holiday Type Day Index Weekday
11-10-2024 fixed d Friday
05-01 yearly d varies per year

The holiday contributions reduce only the affected weekday buckets.

Evaluating pairs, removing Friday and Saturday minimizes the number of already-free holiday overlaps, maximizing remaining working days.

Output:

Friday Saturday

This confirms that the optimal pair is driven by aligning weekend removal with dense weekday clusters.

Example 2

Input:

01-01-2024 14-01-2024
2
01-01
07-01

We compute weekday frequencies over two weeks. Holidays fall on Monday and Sunday.

If we choose Monday and Sunday as weekends, we lose mostly holiday overlaps rather than productive weekdays, improving total working days compared to other pairs.

Pair Removed weekdays Holiday overlap Working effect
Monday Sunday moderate high overlap best
Friday Saturday same weekday removal low overlap worse

Output depends on computed frequencies but follows the same principle.

Complexity Analysis

Measure Complexity Explanation
Time O(D + H + 49T) one scan over days, one over holidays, constant pair evaluation
Space O(1) fixed arrays for 7 weekdays

The total number of days across all test cases is small enough that a linear sweep per case is safe. The constant factor remains low because all heavy work is reduced to simple integer arithmetic and fixed-size arrays.

Test Cases

import sys, io

def run(inp: str) -> str:
    sys.stdin = io.StringIO(inp)
    import sys as _sys
    from contextlib import redirect_stdout
    out = io.StringIO()
    with redirect_stdout(out):
        main()
    return out.getvalue().strip()

# minimal range
assert run("""01-01-1900 01-01-1900
0
""") in WEEK

# single week span
assert run("""01-01-2024 07-01-2024
0
""") in WEEK

# all holidays on same weekday
assert run("""01-01-2024 14-01-2024
2
01-01
08-01
""") in WEEK

# leap year handling
assert run("""28-02-2024 01-03-2024
1
29-02
""") in WEEK

# full month stress small
assert run("""01-01-2024 31-01-2024
3
01-01
15-01
20-01
""") in WEEK
Test input Expected output What it validates
minimal range any valid pair single-day boundary
one week deterministic pair full weekday coverage
repeated weekday holidays valid pair overlap handling
leap year valid pair February 29 correctness
month range valid pair general correctness

Edge Cases

A first edge case is the leap day. If a naive expansion treats February 29 as valid in all years, the weekday index shifts incorrectly after February in non-leap years. The algorithm avoids this by explicitly skipping invalid leap days when expanding yearly holidays.

Another edge case is when the date range starts or ends mid-week. Since weekday frequencies are computed by iterating from the exact start index, no assumption is made about alignment, so partial weeks are naturally handled.

A third case is when all holidays fall on the same weekday that is later chosen as a weekend. In that situation, the hol array correctly subtracts these overlaps, ensuring we do not double penalize those days as both holidays and weekends.