CF 2193E - Product Queries
We are given an array whose values lie between 1 and n. An element may be reused any number of times, so the only thing that matters is which values are present in the array, not how many times they occur.
Rating: 1300
Tags: dp, math, number theory, shortest paths
Solve time: 2m 35s
Verified: yes
Solution
Problem Understanding
We are given an array whose values lie between 1 and n. An element may be reused any number of times, so the only thing that matters is which values are present in the array, not how many times they occur.
For every integer i from 1 to n, we must find the minimum number of chosen array values whose product is exactly i. If no such product can be formed, we output -1.
A useful way to view the problem is as a multiplicative graph. If a value k appears in the array, then from a product x we may move to x * k, provided the result does not exceed n. Every multiplication uses one more selected element.
The total sum of n over all test cases is at most 3 · 10^5. Any solution close to O(n^2) is immediately ruled out. We need something around O(n log n) across a test case.
Several edge cases are easy to mishandle.
Consider:
n = 3
a = [1, 1, 1]
The product 1 is obtainable with one chosen element, so the answer for 1 is 1, not 0. The statement requires selecting at least one element.
Consider:
n = 4
a = [2]
The product 4 is obtainable as 2 · 2, even though the array contains only one occurrence of 2. Reuse is allowed.
Consider:
n = 5
a = [2, 4]
The product 3 is impossible. A careless DP that assumes every number can be built from smaller numbers would incorrectly mark it reachable.
Approaches
The brute-force idea is to treat every product as a state and run a shortest-path search. From a product x, try multiplying by every available array value. This is correct because every multiplication corresponds to choosing one more element.
The problem is the number of transitions. If there are Θ(n) distinct available values and we examine all of them from every state, the complexity becomes Θ(n^2), which is far too large for n = 3 · 10^5.
The key observation is that all products are at most n, and every transition has the form
x -> x * k
where k is a value present in the array.
Since k ≥ 2 for every useful multiplication, the destination is strictly larger than the source. The graph is a DAG whose topological order is simply increasing numerical order.
Let dp[x] be the minimum number of selected elements needed to obtain product x, assuming we start from the neutral product 1 with cost 0.
When we process x, every available multiplier k generates a relaxation:
dp[x * k] = min(dp[x * k], dp[x] + 1)
Instead of iterating over all available values for every state, we iterate over all possible multipliers k up to n / x and simply check whether k is present in the array.
The total number of examined pairs is
Σ floor(n / x)
which is O(n log n).
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(n²) | O(n) | Too slow |
| Optimal | O(n log n) | O(n) | Accepted |
Algorithm Walkthrough
- Build a boolean array
present, wherepresent[v]is true if valuevappears in the array. - Create a DP array initialized with infinity.
- Set
dp[1] = 0. This represents the empty product before choosing any elements. - Process numbers
xfrom1tonin increasing order. - If
dp[x]is still infinity, skip it because productxcannot be formed. - For every multiplier
kfrom2to⌊n / x⌋:
If present[k] is true, relax
dp[x * k] = min(dp[x * k], dp[x] + 1)
The multiplication by k corresponds to choosing one additional array element.
7. Build the answers.
For i > 1, the answer is dp[i] if it is finite, otherwise -1.
For i = 1, the answer is:
1 if present[1]
-1 otherwise
We cannot output 0 because at least one element must be selected.
Why it works
The graph contains an edge from x to x * k whenever value k exists in the array. Every edge has cost 1, and every edge goes to a strictly larger node. Increasing numerical order is therefore a valid topological order.
When processing x, dp[x] already equals the shortest path length from product 1 to x. Relaxing all outgoing edges propagates optimal values to larger products. Since every path corresponds exactly to a sequence of chosen array elements, dp[i] becomes the minimum number of selected elements whose product is i.
Python Solution
import sys
input = sys.stdin.readline
def solve():
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
present = [False] * (n + 1)
for x in a:
present[x] = True
INF = 10 ** 9
dp = [INF] * (n + 1)
dp[1] = 0
for x in range(1, n + 1):
if dp[x] == INF:
continue
limit = n // x
for k in range(2, limit + 1):
if present[k]:
nx = x * k
if dp[nx] > dp[x] + 1:
dp[nx] = dp[x] + 1
ans = []
if present[1]:
ans.append("1")
else:
ans.append("-1")
for i in range(2, n + 1):
if dp[i] == INF:
ans.append("-1")
else:
ans.append(str(dp[i]))
print(" ".join(ans))
solve()
The present array removes all duplicate values. Since elements may be reused infinitely many times, only existence matters.
The DP starts from product 1 with cost 0. This is a virtual starting state and is not itself a valid answer. That is why the answer for product 1 is handled separately.
The loop order is crucial. Processing x in increasing order works because every transition goes to a larger number. No priority queue or repeated relaxation is needed.
Another subtle point is that multipliers start from 2. Multiplying by 1 never changes the product and would create self-loops. Such transitions are useless for shortest paths and can be ignored.
Worked Examples
Example 1
Input:
n = 8
a = [3, 2, 2, 3, 7, 3, 6, 7]
Present values are {2, 3, 6, 7}.
| x | dp[x] before | Relaxations |
|---|---|---|
| 1 | 0 | dp[2]=1, dp[3]=1, dp[6]=1, dp[7]=1 |
| 2 | 1 | dp[4]=2 |
| 3 | 1 | dp[6] stays 1 |
| 4 | 2 | dp[8]=3 |
| 6 | 1 | no useful update |
| 7 | 1 | no useful update |
Final answers:
-1 1 1 2 -1 1 1 3
This example shows how repeated use of the same value creates products such as 8 = 2·2·2.
Example 2
Input:
n = 5
a = [1, 2, 3, 4, 5]
| x | dp[x] before | Relaxations |
|---|---|---|
| 1 | 0 | dp[2]=1, dp[3]=1, dp[4]=1, dp[5]=1 |
| 2 | 1 | no improvement |
| 3 | 1 | no improvement |
| 4 | 1 | no improvement |
| 5 | 1 | no improvement |
Since every target already appears in the array, every answer is 1.
1 1 1 1 1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | The inner loop performs Σ floor(n / x) iterations |
| Space | O(n) | present and dp arrays |
The harmonic-series bound gives roughly n log n operations per test case. With the total sum of n bounded by 3 · 10^5, this easily fits within the limits.
Test Cases
# helper: run solution on input string, return output string
import sys
import io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
input = sys.stdin.readline
out = []
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
present = [False] * (n + 1)
for x in a:
present[x] = True
INF = 10 ** 9
dp = [INF] * (n + 1)
dp[1] = 0
for x in range(1, n + 1):
if dp[x] == INF:
continue
for k in range(2, n // x + 1):
if present[k]:
dp[x * k] = min(dp[x * k], dp[x] + 1)
ans = []
ans.append("1" if present[1] else "-1")
for i in range(2, n + 1):
ans.append(str(dp[i]) if dp[i] < INF else "-1")
out.append(" ".join(ans))
return "\n".join(out)
# provided sample
assert run(
"""6
8
3 2 2 3 7 3 6 7
5
1 2 3 4 5
3
1 1 1
10
2 1 2 1 3 5 5 7 7 7
4
1 1 2 2
1
1
"""
) == (
"""-1 1 1 2 -1 1 1 3
1 1 1 1 1
1 -1 -1
1 1 1 2 1 2 1 3 2 2
1 1 -1 2
1"""
)
# minimum size
assert run(
"""1
1
1
"""
) == "1"
# only value 2
assert run(
"""1
4
2 2 2 2
"""
) == "-1 1 -1 2"
# only ones
assert run(
"""1
5
1 1 1 1 1
"""
) == "1 -1 -1 -1 -1"
# composite reachable through reuse
assert run(
"""1
8
2 3 2 3 2 3 2 3
"""
) == "-1 1 1 2 -1 2 -1 3"
| Test input | Expected output | What it validates |
|---|---|---|
n=1, [1] |
1 |
Smallest valid instance |
n=4, [2,2,2,2] |
-1 1 -1 2 |
Reusing the same value multiple times |
n=5, [1,1,1,1,1] |
1 -1 -1 -1 -1 |
Product 1 handling |
n=8, only 2 and 3 present |
-1 1 1 2 -1 2 -1 3 |
Multi-step multiplicative paths |
Edge Cases
Consider:
1
3
1 1 1
The DP keeps dp[1] = 0, but the final answer for product 1 is not taken from the DP. The algorithm checks present[1] and outputs 1, satisfying the requirement that at least one element must be selected.
Consider:
1
4
2 2 2 2
Processing x = 1 creates dp[2] = 1. Later, processing x = 2 creates dp[4] = 2. The algorithm correctly allows repeated use of the same value even though only one occurrence exists in the array.
Consider:
1
5
2 4 4 2 2
No transition can ever reach 3 or 5. Their DP values remain infinity, so the algorithm outputs -1 for both targets. This prevents unreachable products from being marked as valid.