CF 106045C - Cursed Queries
We have an array and a fixed integer $m$, called the cursed number. Two kinds of operations must be processed online. An update replaces one array element with a new value. A query looks at a subarray and a value $k$, then asks how many elements of that subarray are k-good.
Rating: -
Tags: -
Solve time: 58s
Verified: yes
Solution
Problem Understanding
We have an array and a fixed integer $m$, called the cursed number.
Two kinds of operations must be processed online. An update replaces one array element with a new value. A query looks at a subarray and a value $k$, then asks how many elements of that subarray are k-good.
The definition is slightly indirect. A number is considered good if it is not divisible by $m$. A number $x$ is k-good if after adding $k$ any number of times, it always remains good. In other words, none of the values
$$x,; x+k,; x+2k,; x+3k,\dots$$
may ever become divisible by $m$.
The input contains up to $10^4$ test cases, but the important constraint is that the total $n$ and total $q$ over all test cases are at most $10^5$. Any solution around $O((n+q)\log n)$ per test case is easily fast enough. A quadratic scan over a subarray for every query would be far too slow because a single test could already contain $10^5$ operations.
The main difficulty is understanding the k-good condition. Once that is simplified, the problem becomes a dynamic range counting problem.
Consider a few situations that can easily lead to wrong reasoning.
If $m=8$, $k=12$, and $x=1$, then the sequence modulo $8$ is
$$1,5,1,5,\dots$$
It never reaches $0$, so $x$ is k-good.
If $m=8$, $k=12$, and $x=4$, then modulo $8$ we get
$$4,0,4,0,\dots$$
The second term is divisible by $8$, so $x$ is not k-good.
A common mistake is to test only whether $x$ itself is divisible by $m$. The definition requires checking all future additions as well.
Another subtle case appears when $k$ is a multiple of $m$. For example, $m=10$, $k=20$. Adding $k$ never changes the residue modulo $m$. Then a number is k-good exactly when it is not divisible by $10$. Any solution that treats all values of $k$ uniformly without examining $\gcd(k,m)$ will miss this behavior.
Approaches
The brute force interpretation follows the definition directly. For every query, inspect each element of the subarray and determine whether repeated additions of $k$ can ever produce a multiple of $m$.
One way to do that is to work modulo $m$. Starting from $a_i \bmod m$, repeatedly add $k \bmod m$ until the sequence repeats. Since there are only $m$ residues, this takes $O(m)$ time per element.
With $m \le 5000$, a query on a range of length $10^5$ could require roughly
$$10^5 \cdot 5000$$
operations, which is completely infeasible.
The key observation comes from modular arithmetic.
We want to know whether there exists some $t \ge 0$ such that
$$a_i + t k \equiv 0 \pmod m.$$
This linear congruence has a solution if and only if
$$\gcd(k,m) \mid a_i.$$
Let
$$g = \gcd(k,m).$$
Then:
$$a_i \text{ is k-good} \iff g \nmid a_i.$$
The entire complicated definition collapses into a simple divisibility test.
Now the query becomes:
Count elements in $[l,r]$ that are not divisible by $g$.
Since $m$ never changes and $g$ must be a divisor of $m$, only divisors of $m$ can ever appear in queries. The number of divisors of any integer up to $5000$ is small, at most a few dozen.
For every divisor $d$ of $m$, maintain a Fenwick tree storing which positions currently contain numbers divisible by $d$.
Then:
$$\text{answer}
(r-l+1)
#{\text{elements divisible by } g}.$$
Updates modify one position, so we update every divisor tree once.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | $O((r-l+1)\cdot m)$ per query | $O(1)$ | Too slow |
| Optimal | $O(D\log n)$ per operation | $O(Dn)$ | Accepted |
Here $D$ is the number of divisors of $m$.
Algorithm Walkthrough
- Compute all divisors of $m$.
- Create one Fenwick tree for each divisor $d$.
- For every array position $i$, insert value $1$ into divisor tree $d$ if $a_i$ is divisible by $d$.
- For an update
1 i x, remove the contribution of the old value from every divisor tree and add the contribution of the new value.
The number of divisors is small, so touching every divisor is inexpensive.
5. For a query 2 l r k, compute
$$g=\gcd(k,m).$$
6. Use the Fenwick tree corresponding to divisor $g$ to count how many values in $[l,r]$ are divisible by $g$.
7. Let that count be bad. The number of k-good elements is
$$(r-l+1)-bad.$$ 8. Output the result.
Why it works
The crucial property is the classical linear congruence result:
$$a + tk \equiv 0 \pmod m$$
has a solution if and only if
$$\gcd(k,m)\mid a.$$
If $g=\gcd(k,m)$ divides $a$, some future term becomes divisible by $m$, so the number is not k-good.
If $g$ does not divide $a$, no term in the entire arithmetic progression can ever become divisible by $m$, so the number is k-good.
Thus every query reduces exactly to counting values not divisible by $g$, and the Fenwick trees maintain those divisibility counts correctly under updates.
Python Solution
import sys
from math import gcd
input = sys.stdin.readline
class Fenwick:
def __init__(self, n):
self.n = n
self.bit = [0] * (n + 1)
def add(self, idx, delta):
n = self.n
bit = self.bit
while idx <= n:
bit[idx] += delta
idx += idx & -idx
def sum(self, idx):
bit = self.bit
res = 0
while idx > 0:
res += bit[idx]
idx -= idx & -idx
return res
def range_sum(self, l, r):
return self.sum(r) - self.sum(l - 1)
def solve():
t = int(input())
out = []
for _ in range(t):
n, m = map(int, input().split())
a = list(map(int, input().split()))
divisors = []
for d in range(1, m + 1):
if m % d == 0:
divisors.append(d)
idx_of = {d: i for i, d in enumerate(divisors)}
trees = [Fenwick(n) for _ in divisors]
for pos, val in enumerate(a, start=1):
for j, d in enumerate(divisors):
if val % d == 0:
trees[j].add(pos, 1)
q = int(input())
for _ in range(q):
query = list(map(int, input().split()))
if query[0] == 1:
_, i, x = query
old = a[i - 1]
for j, d in enumerate(divisors):
old_div = (old % d == 0)
new_div = (x % d == 0)
if old_div != new_div:
trees[j].add(i, 1 if new_div else -1)
a[i - 1] = x
else:
_, l, r, k = query
g = gcd(k, m)
tree = trees[idx_of[g]]
divisible = tree.range_sum(l, r)
ans = (r - l + 1) - divisible
out.append(str(ans))
sys.stdout.write("\n".join(out))
solve()
The first part computes all divisors of $m$. Since every possible query uses $g=\gcd(k,m)$, and every such $g$ must be a divisor of $m$, these are the only divisibility classes we ever need.
Each Fenwick tree stores, for one divisor $d$, whether each position currently contains a multiple of $d$. A range sum immediately gives the number of multiples of $d$ inside any interval.
The update operation compares the old and new divisibility status for every divisor. Only when the status changes do we modify the Fenwick tree. This avoids unnecessary updates.
The query operation computes $g=\gcd(k,m)$, finds the corresponding Fenwick tree, counts multiples of $g$ in the range, and subtracts from the interval length.
All arithmetic fits comfortably inside Python integers, so there are no overflow concerns.
Worked Examples
Example 1
Input:
n = 5, m = 10
a = [1, 2, 3, 4, 5]
query: 2 1 5 2
Here:
$$g=\gcd(2,10)=2.$$
| Position | Value | Divisible by 2? |
|---|---|---|
| 1 | 1 | No |
| 2 | 2 | Yes |
| 3 | 3 | No |
| 4 | 4 | Yes |
| 5 | 5 | No |
The Fenwick tree for divisor 2 reports 2 divisible elements.
| l | r | Length | Divisible by g | Answer |
|---|---|---|---|---|
| 1 | 5 | 5 | 2 | 3 |
Output:
3
This demonstrates the main reduction. We never simulate repeated additions of $k$.
Example 2
Input:
n = 4, m = 8
a = [1, 8, 6, 5]
update: 1 2 9
query: 2 1 4 12
After the update:
[1, 9, 6, 5]
$$g=\gcd(12,8)=4.$$
| Position | Value | Divisible by 4? |
|---|---|---|
| 1 | 1 | No |
| 2 | 9 | No |
| 3 | 6 | No |
| 4 | 5 | No |
| Length | Divisible by 4 | Answer |
|---|---|---|
| 4 | 0 | 4 |
Output:
4
This trace shows how updates propagate through the divisor structures before answering later queries.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(D \log n)$ per operation | Every update or query touches at most one Fenwick tree per divisor |
| Space | $O(Dn)$ | One Fenwick tree for each divisor of $m$ |
Since $m \le 5000$, the number of divisors $D$ is small. Combined with the global limit of $10^5$ array elements and queries, this comfortably fits within the limits.
Test Cases
# helper: run solution on input string, return output string
import sys
import io
from math import gcd
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
out = io.StringIO()
input = sys.stdin.readline
class Fenwick:
def __init__(self, n):
self.n = n
self.bit = [0] * (n + 1)
def add(self, i, v):
while i <= self.n:
self.bit[i] += v
i += i & -i
def sum(self, i):
s = 0
while i:
s += self.bit[i]
i -= i & -i
return s
t = int(input())
ans = []
for _ in range(t):
n, m = map(int, input().split())
a = list(map(int, input().split()))
divs = [d for d in range(1, m + 1) if m % d == 0]
mp = {d: i for i, d in enumerate(divs)}
fw = [Fenwick(n) for _ in divs]
for p, val in enumerate(a, start=1):
for j, d in enumerate(divs):
if val % d == 0:
fw[j].add(p, 1)
q = int(input())
for _ in range(q):
qv = list(map(int, input().split()))
if qv[0] == 1:
_, i, x = qv
old = a[i - 1]
for j, d in enumerate(divs):
if (old % d == 0) != (x % d == 0):
fw[j].add(i, 1 if x % d == 0 else -1)
a[i - 1] = x
else:
_, l, r, k = qv
g = gcd(k, m)
tree = fw[mp[g]]
divcnt = tree.sum(r) - tree.sum(l - 1)
ans.append(str((r - l + 1) - divcnt))
return "\n".join(ans)
# sample 1
assert run(
"""1
5 10
1 2 3 4 5
5
2 1 5 1
2 1 5 2
2 1 5 3
2 1 5 4
2 1 5 5
"""
) == "0\n3\n0\n3\n0"
# minimum size
assert run(
"""1
1 7
1
1
2 1 1 14
"""
) == "1"
# all equal values
assert run(
"""1
4 6
6 6 6 6
1
2 1 4 3
"""
) == "0"
# update changes answer
assert run(
"""1
3 8
1 8 5
3
2 1 3 4
1 2 7
2 1 3 4
"""
) == "2\n3"
# boundary interval
assert run(
"""1
3 12
4 6 9
1
2 2 2 18
"""
) == "0"
| Test input | Expected output | What it validates |
|---|---|---|
| Single element, $m=7$ | 1 | Minimum size case |
| All values equal to $m$ | 0 | Every element divisible by the relevant gcd |
| Update replacing a divisible value | 2 then 3 | Correct update propagation |
| Query on a single position | 0 | Range boundary handling |
Edge Cases
Case 1: $k$ is a multiple of $m$
Input:
1
3 10
10 11 12
1
2 1 3 20
Here
$$g=\gcd(20,10)=10.$$
Only the first value is divisible by 10.
The Fenwick tree for divisor 10 reports one divisible element in the range. The interval length is 3, so the answer is
$$3-1=2.$$
This matches the actual behavior because adding 20 never changes a residue modulo 10.
Case 2: Value not divisible by $m$, but still not k-good
Input:
1
1 8
4
1
2 1 1 12
We have
$$g=\gcd(12,8)=4.$$
The value 4 is divisible by 4, so it is counted as not k-good.
Indeed,
$$4+12=16,$$
which is divisible by 8.
The algorithm outputs 0, which is correct.
Case 3: Single-position range after updates
Input:
1
1 6
6
2
1 1 5
2 1 1 3
After the update, the value becomes 5.
$$g=\gcd(3,6)=3.$$
Since 5 is not divisible by 3, the Fenwick tree reports zero divisible elements.
The answer is
$$1-0=1.$$
This confirms that updates and one-element ranges work correctly together.