CF 475D - CGCDSSQ
We are given a list of integers and a set of queries. Each query asks how many contiguous subarrays of the list have a greatest common divisor equal to the query value.
Rating: 2000
Tags: brute force, data structures, math
Solve time: 1m 11s
Verified: yes
Solution
Problem Understanding
We are given a list of integers and a set of queries. Each query asks how many contiguous subarrays of the list have a greatest common divisor equal to the query value. In simpler terms, for each number in the queries, we want to know how many stretches of consecutive elements in the array share that number as their largest shared divisor. For instance, if the array is [2, 6, 3] and the query is 2, the subarrays [2] and [2, 6] have GCD equal to 2, giving a count of 2.
The array can have up to 10^5 elements, and the number of queries can reach 3 × 10^5. Each element and each query can be as large as 10^9. This combination of high n and high q immediately rules out any solution that considers every subarray individually. A naive approach would require examining O(n^2) subarrays, which with n = 10^5 results in roughly 5 × 10^9 operations, far exceeding a 2-second time limit. Each query also cannot be processed independently by rescanning the array, as that would multiply the work by q.
Non-obvious edge cases appear when elements are repeated, or when the GCD of long stretches reduces quickly. For example, in the array [4, 8, 16] and query 2, naive scanning may miss subarrays like [8, 16] whose GCD is 8 rather than 2, or [16] whose GCD is 16. Another subtle case occurs when a query asks for a number that is not a divisor of any element; the answer should be 0, not a miscount of near matches.
Approaches
The brute-force solution would iterate over all possible starting indices, then expand each subarray to the right, computing the GCD incrementally. For every expansion, we would check if the current GCD matches any query. This is correct because it literally counts every subarray, but the worst-case complexity is O(n^2), which fails for n up to 10^5.
The key insight to improve efficiency is that the GCD function is non-increasing when extending a subarray to the right. Once we know the GCD of a subarray ending at position i, extending it by including the next element will only reduce the GCD or leave it unchanged. This means we can maintain a mapping from possible GCDs to the number of subarrays ending at the current position. For each element, we take all previous GCDs, combine them with the current element using GCD, and aggregate counts. This dynamic programming approach produces all possible subarray GCDs in linear time relative to the number of unique GCD values seen at each step. The observation that the number of distinct GCDs at each step is bounded by O(log max(a_i)) ensures efficiency.
This approach converts an O(n^2) problem into something closer to O(n log A) where A is the maximum element. It also allows us to precompute counts for all possible GCDs, making query answering O(1) per query.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(n^2) | O(1) | Too slow |
| GCD Aggregation | O(n log A + q) | O(n log A) | Accepted |
Algorithm Walkthrough
- Initialize a dictionary
count_gcdto store cumulative counts of each GCD encountered across all subarrays. This will allow direct query answers after processing the array. - Initialize another dictionary
current_gcdswhich maps a GCD value to the number of subarrays ending at the current element that have that GCD. Start empty. - Iterate over each element
a[i]in the array. For each step, initializenext_gcdsas an empty dictionary to store the new GCD counts for subarrays ending ati. - Include the subarray containing only
a[i]itself. Setnext_gcds[a[i]] = 1. This handles single-element subarrays. - For each GCD
gincurrent_gcds, computegcd(g, a[i])and incrementnext_gcds[gcd(g, a[i])]by the countcurrent_gcds[g]. This updates counts for all extended subarrays. - Merge
next_gcdsintocount_gcdby adding their counts. This keeps the total counts for every GCD encountered so far. - Replace
current_gcdswithnext_gcdsand continue to the next element. - After processing the entire array, each query
xcan be answered by looking upcount_gcd.get(x, 0).
Why it works: the algorithm maintains the invariant that current_gcds correctly counts the number of subarrays ending at the current index for each possible GCD. By iterating this over the array, all possible subarrays are accounted for exactly once. The non-increasing property of GCD ensures no subarray GCD is missed or double-counted.
Python Solution
import sys
import math
input = sys.stdin.readline
n = int(input())
a = list(map(int, input().split()))
q = int(input())
queries = [int(input()) for _ in range(q)]
from collections import defaultdict
count_gcd = defaultdict(int)
current_gcds = {}
for num in a:
next_gcds = defaultdict(int)
next_gcds[num] += 1
for g, cnt in current_gcds.items():
new_g = math.gcd(g, num)
next_gcds[new_g] += cnt
for g, cnt in next_gcds.items():
count_gcd[g] += cnt
current_gcds = next_gcds
for x in queries:
print(count_gcd.get(x, 0))
The first block reads the input and prepares query storage. The count_gcd dictionary accumulates global counts while current_gcds keeps track of subarrays ending at the current position. Using math.gcd ensures correctness for every combination, and defaultdict(int) prevents KeyErrors. Incrementing counts rather than overwriting is critical to maintain correct counts for subarrays sharing the same GCD. The final loop prints results directly from the precomputed dictionary.
Worked Examples
Consider the sample input [2, 6, 3] with queries [1, 2, 3, 4, 6].
| Step | current_gcds | next_gcds | count_gcd |
|---|---|---|---|
| 2 | {} | {2:1} | {2:1} |
| 6 | {2:1} | {6:1, 2:1} | {2:2, 6:1} |
| 3 | {6:1, 2:1} | {3:2,1:1} | {2:2,6:1,3:2,1:1} |
This trace shows how each subarray ending at each position contributes to total GCD counts. The counts match expected answers for the queries: 1 maps to 1, 2 maps to 2, 3 maps to 2, 4 maps to 0, 6 maps to 1.
Another example: array [4, 8, 16] and query [4,8,16,2].
| Step | current_gcds | next_gcds | count_gcd |
|---|---|---|---|
| 4 | {} | {4:1} | {4:1} |
| 8 | {4:1} | {8:1,4:1} | {4:2,8:1} |
| 16 | {8:1,4:1} | {16:1,8:1,4:1} | {4:3,8:2,16:1} |
All subarrays are correctly counted, including overlapping GCDs.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log A + q) | At each element, we extend up to O(log A) distinct GCDs, with q queries accessed in O(1) each. |
| Space | O(n log A) | The dictionaries store counts of distinct GCDs seen so far; each can be O(log A) per element. |
With n up to 10^5 and A up to 10^9, log A ≈ 30, so roughly 3 × 10^6 operations, comfortably within 2 seconds.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
output = io.StringIO()
sys.stdout = output
exec(open("solution.py").read()) # assume solution in solution.py
return output.getvalue().strip()
# Provided sample
assert run("3\n2 6 3\n5\n1\n2\n3\n4\n6\n") == "1\n2\n2\n0\n1"
# Minimum-size input
assert run("1\n7\n3\n1\n7\n10\n") == "0\n1\n0"
# All-equal elements
assert run("3\n5 5 5