|
| 1 | +--- |
| 2 | +id: cf-718C |
| 3 | +source: CF |
| 4 | +title: Sasha and Array |
| 5 | +author: Dustin Miao |
| 6 | +--- |
| 7 | + |
| 8 | +## Explanation |
| 9 | + |
| 10 | +Firstly, a note on notation: let $F_i$ denote the $i$th Fibonacci number. That is, |
| 11 | + |
| 12 | +$$ |
| 13 | +F_i = |
| 14 | +\begin{cases} |
| 15 | + 0 & i = 0 \\ |
| 16 | + 1 & i = 1 \\ |
| 17 | + F_{i - 1} + F_{i - 2} & i \geq 2 \\ |
| 18 | +\end{cases} |
| 19 | +$$ |
| 20 | + |
| 21 | +In this problem, we will build a segment tree over the array. For a leaf node in the segment tree with value $v$, we will store a pair of values $(F_{v - 1}, F_v)$. For all non-leaf nodes, we will store a pair of values equal to the pair-sum of its children. That is, if a node $u$ has children $v$ and $w$, then $$u = (v_0 + w_0, v_1 + w_1)$$. |
| 22 | + |
| 23 | +We will use the term _cycle by k_ to denote transforming some pair $(F_{i - 1}, F_i)$ to $(F_{i - 1 + k}, F_{i + k})$. By default, the term _cycle_ refers to _cycle by 1_. |
| 24 | + |
| 25 | +For each update, we need to cycle each leaf node by some amount $x$ such that the value in a node affected goes from $(F_{v - 1}, F_v)$ to $(F_{v - 1 + x}, F_{v + x})$. We can do this using matrix exponentiation. |
| 26 | + |
| 27 | +First note that for a matrix $\begin{pmatrix} F_{i - 1} & F_i \end{pmatrix}$, we can cycle it by multiplying by $\begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix}$. That is, |
| 28 | + |
| 29 | +$$ |
| 30 | +\begin{pmatrix} F_i & F_{i + 1} \end{pmatrix} = \begin{pmatrix} F_{i - 1} & F_i \end{pmatrix}\begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix} |
| 31 | +$$ |
| 32 | + |
| 33 | +Inductively, we can cycle by $k$ by multiplying multiple times: |
| 34 | + |
| 35 | +$$ |
| 36 | +\begin{pmatrix} F_{i - 1 + k} & F_{i + k} \end{pmatrix} = \begin{pmatrix} F_{i - 1} & F_i \end{pmatrix}\begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix}^k |
| 37 | +$$ |
| 38 | + |
| 39 | +We can use binary exponentiation to quickly compute powers of the two-by-two matrix. |
| 40 | + |
| 41 | +Secondly, we need to utilize the distributive property of matrix multiplication to same-size matrices. That is, if $m_k$ is a one-by-two matrix for $k \in [1, n]$, then the following property is satisfied: |
| 42 | + |
| 43 | +$$ |
| 44 | +(m_1 + m_2 + \dots + m_n) \cdot \begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix} = m_1 \cdot \begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix} + m_2 \cdot \begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix} + \dots + m_n \cdot \begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix} |
| 45 | +$$ |
| 46 | + |
| 47 | +In that way, we can utilize lazy propagation on the segment tree. We will store an integer tag in each node denoting the lazy update, which denotes that we must cycle every node in its subtree by the value of the tag. When propagating, we can simply update the node by binary exponentiating and then multiplying, then storing it in the lazy tag. Querying works as normal on the segment tree. |
| 48 | + |
| 49 | +## Implementation |
| 50 | + |
| 51 | +<LanguageSection> |
| 52 | + |
| 53 | +<CPPSection> |
| 54 | + |
| 55 | +```cpp |
| 56 | +#include <bits/stdc++.h> |
| 57 | +using namespace std; |
| 58 | + |
| 59 | +#define f first |
| 60 | +#define s second |
| 61 | + |
| 62 | +using ll = long long; |
| 63 | +using pll = pair<ll, ll>; |
| 64 | + |
| 65 | +const int MAXN = 1e5 + 1; |
| 66 | +const ll MOD = 1e9 + 7; |
| 67 | + |
| 68 | + |
| 69 | +//BeginCodeSnip{Matrix Operations} |
| 70 | +using Matrix = array<array<ll, 2>, 2>; |
| 71 | + |
| 72 | +// multiplies two two-by-two matricies |
| 73 | +Matrix multiply(const Matrix &a, const Matrix &b) { |
| 74 | + return {(a[0][0] * b[0][0] + a[0][1] * b[1][0]) % MOD, |
| 75 | + (a[0][0] * b[0][1] + a[0][1] * b[1][1]) % MOD, |
| 76 | + (a[1][0] * b[0][0] + a[1][1] * b[1][0]) % MOD, |
| 77 | + (a[1][0] * b[0][1] + a[1][1] * b[1][1]) % MOD}; |
| 78 | +} |
| 79 | + |
| 80 | +// multiplies a one-by-two matrix with a two-by-two matrix |
| 81 | +pll multiply(const pll &a, const Matrix &b) { |
| 82 | + return {(a.f * b[0][0] + a.s * b[1][0]) % MOD, |
| 83 | + (a.f * b[0][1] + a.s * b[1][1]) % MOD}; |
| 84 | +} |
| 85 | + |
| 86 | +// returns {0, 1, 1, 1,} to the power of n |
| 87 | +Matrix pow(ll n) { |
| 88 | + Matrix res = {1, 0, 0, 1}; // identity matrix |
| 89 | + Matrix base = {0, 1, 1, 1}; // fibonacci matrix |
| 90 | + for (; n; n >>= 1) { // binary exponentiation (log n) |
| 91 | + if (n & 1) { |
| 92 | + res = multiply(res, base); |
| 93 | + } |
| 94 | + base = multiply(base, base); |
| 95 | + } |
| 96 | + return res; |
| 97 | +} |
| 98 | +//EndCodeSnip |
| 99 | + |
| 100 | +int N, Q; |
| 101 | +pll tree[MAXN * 4]; |
| 102 | +ll lazy[MAXN * 4]; |
| 103 | + |
| 104 | +// returns the pair-sum of a and b |
| 105 | +pll merge(const pll &a, const pll &b) { |
| 106 | + return {(a.f + b.f) % MOD, (a.s + b.s) % MOD}; |
| 107 | +} |
| 108 | + |
| 109 | +// pushes lazy update in t to its children |
| 110 | +void pushdown(int t) { |
| 111 | + if (lazy[t] == 0) |
| 112 | + return; |
| 113 | + tree[t << 1] = multiply(tree[t << 1], pow(lazy[t])); |
| 114 | + lazy[t << 1] += lazy[t]; |
| 115 | + tree[t << 1 | 1] = multiply(tree[t << 1 | 1], pow(lazy[t])); |
| 116 | + lazy[t << 1 | 1] += lazy[t]; |
| 117 | + lazy[t] = 0; |
| 118 | +} |
| 119 | + |
| 120 | +// cycle range from l to r by v |
| 121 | +void update(int l, int r, ll v, int t = 1, int tl = 1, int tr = N) { |
| 122 | + if (r < tl || tr < l) { |
| 123 | + return; |
| 124 | + } |
| 125 | + if (l <= tl && tr <= r) { |
| 126 | + tree[t] = multiply(tree[t], pow(v)); |
| 127 | + lazy[t] += v; |
| 128 | + return; |
| 129 | + } |
| 130 | + pushdown(t); |
| 131 | + |
| 132 | + int tm = (tl + tr) >> 1; |
| 133 | + update(l, r, v, t << 1, tl, tm); |
| 134 | + update(l, r, v, t << 1 | 1, tm + 1, tr); |
| 135 | + tree[t] = merge(tree[t << 1], tree[t << 1 | 1]); |
| 136 | +} |
| 137 | + |
| 138 | +// query sum from l to r |
| 139 | +ll query(int l, int r, int t = 1, int tl = 1, int tr = N) { |
| 140 | + if (r < tl || tr < l) { |
| 141 | + return 0; |
| 142 | + } |
| 143 | + if (l <= tl && tr <= r) { |
| 144 | + return tree[t].f; |
| 145 | + } |
| 146 | + pushdown(t); |
| 147 | + |
| 148 | + int tm = (tl + tr) >> 1; |
| 149 | + return (query(l, r, t << 1, tl, tm) + query(l, r, t << 1 | 1, tm + 1, tr)) % MOD; |
| 150 | +} |
| 151 | + |
| 152 | +int main() { |
| 153 | + cin >> N >> Q; |
| 154 | + fill_n(tree, MAXN * 4, pll{0, 1}); |
| 155 | + for (int i = 1; i <= N; i++) { |
| 156 | + ll a; |
| 157 | + cin >> a; |
| 158 | + update(i, i, a); |
| 159 | + } |
| 160 | + |
| 161 | + while (Q--) { |
| 162 | + int t; |
| 163 | + cin >> t; |
| 164 | + if (t == 1) { |
| 165 | + int l, r; |
| 166 | + ll v; |
| 167 | + cin >> l >> r >> v; |
| 168 | + update(l, r, v); |
| 169 | + } else if (t == 2) { |
| 170 | + int l, r; |
| 171 | + cin >> l >> r; |
| 172 | + cout << query(l, r) << '\n'; |
| 173 | + } |
| 174 | + } |
| 175 | +} |
| 176 | +``` |
| 177 | +</CPPSection> |
| 178 | + |
| 179 | +</LanguageSection> |
0 commit comments