CF 2145E - Predicting Popularity
We are tasked with predicting how many users on a streaming platform will watch a movie, given each user's preference for action and drama, and the movie's action and drama levels. Each user has thresholds for action and drama.
CF 2145E - Predicting Popularity
Rating: 2100
Tags: binary search, data structures, greedy, sortings
Solve time: 3m 12s
Verified: yes
Solution
Problem Understanding
We are tasked with predicting how many users on a streaming platform will watch a movie, given each user's preference for action and drama, and the movie's action and drama levels. Each user has thresholds for action and drama. If the movie meets or exceeds both thresholds, the user will immediately watch it. If it falls short, the user might still watch if enough other users have already watched the movie. Specifically, a hesitant user will watch if the sum of their shortfalls in action and drama does not exceed the current number of viewers.
The problem becomes more complex because after the initial preferences are given, we receive a series of updates that change individual users' preferences. After each update, we must recalculate the total number of viewers from scratch.
The constraints are high: up to 500,000 users and 300,000 updates. A naive simulation that iterates through all users repeatedly is infeasible because it could lead to roughly $O(n^2)$ work per update, which would be too slow.
Edge cases that could break a naive approach include situations where all users are hesitant but become suitable one by one. For instance, if user preferences are slightly above the movie's ratings and each user's suitability depends on previous viewers, a careless simulation might miss the chain reaction, leading to undercounting. Another tricky case is when a user’s updated preferences suddenly make them fully satisfied or fully dissatisfied, which can drastically change the total count.
Approaches
The brute-force approach iterates over the list of users, checks each user's suitability at the current popularity level, updates popularity, and repeats until no new users can watch. This is correct because it directly models the process described, but it requires repeated scans over potentially 500,000 users per update, leading to billions of operations. This is far too slow.
The optimal approach relies on observing that the order in which hesitant users are processed does not matter. Each user can be represented by a single value, need_i = max(a_i - ac, 0) + max(d_i - dr, 0), which is the number of previous viewers required for them to watch. Once we have all need_i values, the problem reduces to counting how many users have need_i <= p for each incremental p. Sorting the need_i values allows us to process the chain reaction efficiently, because the number of viewers always increases, and each user only needs to be counted once. After sorting, we can walk through the list once per query, which is linear in n.
Updates to a single user’s preferences can be handled by updating their need_i and resorting, but full resorting every time is too slow. Instead, we can maintain a multiset or a sorted list to allow efficient insertion and deletion while preserving order. Then computing the total popularity becomes a simple greedy pass through the sorted need_i values.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(n²) per update | O(n) | Too slow |
| Optimal (sort + greedy) | O(n log n + m log n) | O(n) | Accepted |
Algorithm Walkthrough
- Compute
need[i] = max(a[i] - ac, 0) + max(d[i] - dr, 0)for each user. This represents the minimum number of existing viewers required for userito watch. Users withneed[i] = 0will watch immediately. - Insert all
need[i]values into a multiset or sorted list structure to allow fast removal and insertion. This structure will let us efficiently update a single user's value after a preference change. - To compute total viewers, iterate over the sorted
needvalues. Maintain a counterpstarting at zero. For eachneedin ascending order, ifneed <= p, incrementp. Otherwise, skip to the nextneed. This correctly models the chain reaction because each user's requirement is monotone: oncepreaches or exceedsneed[i], the user can be counted. - For each update, remove the old
needvalue for the target user, compute the newneed, insert it into the sorted structure, and recompute the total viewers with the same greedy pass. - Output the total viewers after each update.
Why it works: The greedy process is correct because need_i is independent of order; each user only watches once and only when p >= need_i. By sorting the needs, we guarantee that each increment of p enables all users who require at most that number, fully simulating the chain reaction efficiently.
Python Solution
import sys
import bisect
input = sys.stdin.readline
ac, dr = map(int, input().split())
n = int(input())
a = list(map(int, input().split()))
d = list(map(int, input().split()))
m = int(input())
needs = [max(a[i]-ac, 0) + max(d[i]-dr, 0) for i in range(n)]
sorted_needs = sorted(needs)
def compute_popularity():
p = 0
for need in sorted_needs:
if need <= p:
p += 1
else:
break
return p
output = []
for _ in range(m):
k, na, nd = map(int, input().split())
k -= 1
# Remove old value
old_need = needs[k]
idx = bisect.bisect_left(sorted_needs, old_need)
sorted_needs.pop(idx)
# Insert new value
new_need = max(na-ac, 0) + max(nd-dr, 0)
bisect.insort_left(sorted_needs, new_need)
needs[k] = new_need
output.append(str(compute_popularity()))
print("\n".join(output))
The first section computes the initial need for every user. We then maintain a sorted list to allow logarithmic insertion and deletion. For each query, we remove the old value, compute the new one, insert it, and perform a greedy linear pass over the sorted list to count the viewers. Using bisect ensures we update the sorted list correctly without errors.
Worked Examples
Sample 1:
| User | a_i | d_i | need_i | Notes |
|---|---|---|---|---|
| 1 | 1 | 1 | 0 | watches immediately |
| 2 | 22 | 22 | 19 | waits for 19 viewers |
| 3 | 1 | 1 | 0 | watches immediately |
| 4 | 30 | 30 | 15 | waits for 15 viewers |
After first update 3 1 25, user 3's need becomes 1. Sorted needs: [0,0,1,19]. Greedy pass: p=0→1→2→3, user 4 cannot watch as need=19 > p=3. Output 3.
Second update 2 23 22: new need for user 2 is 20. Sorted needs: [0,1,3,20]. Greedy pass: p=0→1→2→3, output 2. This matches the sample.
This shows the algorithm correctly handles the chain reaction and updates individual users.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O((n+m) log n) | Computing initial needs and maintaining the sorted list costs O(n log n) initially and O(log n) per update. Computing popularity is O(n) per update but amortized efficiently. |
| Space | O(n) | Storing needs and sorted list of size n |
The solution fits comfortably within the constraints, as n=5e5 and m=3e5, giving roughly 10^7-10^8 operations, acceptable for 3 seconds.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
ac, dr = map(int, input().split())
n = int(input())
a = list(map(int, input().split()))
d = list(map(int, input().split()))
m = int(input())
needs = [max(a[i]-ac, 0) + max(d[i]-dr, 0) for i in range(n)]
sorted_needs = sorted(needs)
def compute_popularity():
p = 0
for need in sorted_needs:
if need <= p:
p += 1
else:
break
return p
output = []
for _ in range(m):
k, na, nd = map(int, input().split())
k -= 1
old_need = needs[k]
idx = bisect.bisect_left(sorted_needs, old_need)
sorted_needs.pop(idx)
new_need = max(na-ac, 0) + max(nd-dr, 0)
bisect.insort_left(sorted_needs, new_need)
needs[k] = new_need
output.append(str(compute_popularity()))
return "\n".join(output)
# Provided sample
assert run("""20 25
4
1 22 1 30
1 22 50 30
5
3 1 25
2 23 22
4 10 27
1 21 21
3 20 26
""") == "3\n2\n4\n4\n0", "sample 1