<center> <h1> week of 11/14 </h1> usaco pt 2 </center> --- ### `📝 notes` ill be participating in [calico](https://calico.berkeley.edu) this weekend! --- ## misc. ### [Rorororobot](https://codeforces.cc/contest/1709/problem/D) --- #### `problem:` you are given an $n \times m$ grid, with the last $s_i$ blocks of column $i$ blocked off. next, you are given $q$ queries. * you are given $(x,y), (x1,y1), k$ * the robot starts at $(x, y)$ and can freely move around the grid in all $4$ directions. if the robot ever hits a blocked off cell or goes outside the grid, it explodes. * the robot is broken, and every instruction you give it, the robot repeats it $k$ times. figure out whether it's possible to travel between the two cells. #### `solution` firstly, we know that we have to travel a distance of exactly $|c-c1|$, so this value must be divisible by $k$. next, we also have to make sure that it's even possible to get to the other side, taking the blocked cells into account. let's try the largest possible value, $\lfloor (n-r)/k \rfloor \cdot k + r$. if this is less than or equal to the largest value between $c$ and $c-1$, this won't work. we'll query the maximum value between the two with a segment tree. if this row value $- r1$ is divisible by $k$, we're done! ```cpp= #include "bits/stdc++.h" using namespace std; using ll = long long; #define all(x) begin(x), end(x) #define rall(x) x.rbegin(), x.rend() #define sz(x) (int)(x.size()) #define inf 1000000010 #define linf 0x3f3f3f3f3f3f3f3f #ifdef LOCAL #define endl "\n" #include "/mnt/c/yukon/dbg.cpp" #else #define dbg(...) 0 #endif #define int ll template <typename T> ostream& operator<<(ostream& os, const vector<T>& v) { for (int i = 0; i < sz(v); ++i) { os << v[i] << ' '; } return os; } template <class T> struct Seg { // comb(ID,b) = b const T ID = -1e18; T comb(T a, T b) { return max(a, b); } int n; vector<T> seg; void init(int _n) { n = _n; seg.assign(2 * n, ID); } void pull(int p) { seg[p] = comb(seg[2 * p], seg[2 * p + 1]); } void upd(int p, T val) { // set val at position p seg[p += n] = val; for (p /= 2; p; p /= 2) pull(p); } T query(int l, int r) { // max on interval [l, r] T ra = ID, rb = ID; for (l += n, r += n + 1; l < r; l /= 2, r /= 2) { if (l & 1) ra = comb(ra, seg[l++]); if (r & 1) rb = comb(seg[--r], rb); } return comb(ra, rb); } }; signed main() { cin.tie(0)->sync_with_stdio(0); int n, m; cin >> n >> m; Seg<ll> tree; tree.init(m); for (int i = 0; i < m; i++) { ll x; cin >> x; tree.upd(i, x); } int q; cin >> q; for (int i = 0; i < q; i++) { int r, c, r1, c1, k; cin >> r >> c >> r1 >> c1 >> k; if (c == c1) { if (abs(r - r1) % k) { cout << "NO" << endl; } else { cout << "YES" << endl; } continue; } bool ok = 1; if (abs(c1 - c) % k) ok = 0; ll h = ((n - r) / k) * k; h += r; if (abs(h - r1) % k) ok = 0; ll mx = tree.query(min(c - 1, c1 - 1), max(c - 1, c1 - 1)); // cout << mx << endl; if (h <= mx) ok = 0; cout << (ok ? "YES" : "NO") << endl; } } // don't get stuck on one approach // question bounds // flesh out your approach before implementing o.o ``` ### [Paths on the Tree](https://codeforces.cc/contest/1746/problem/D) --- #### `problem:` i don't think the problem statement is that confusing, so here ya go. ![](https://i.imgur.com/FraYl0t.png) * basically any two nodes which share the same parent should not have the number of paths differ by more than $1$. * the score is the number of paths that go through the node $\times s_i$, where $s_i$ is the score array which is given to you. find the max score. #### `solution:` the number of paths which go throuhg the first layer of nodes (nodes which share the parent $1$) should be $\lfloor \frac{n}{\texttt{nadj}} \rfloor$, and $n \mod \texttt{nadj}$ nodes should have $\lfloor \frac{n}{\texttt{nadj}} \rfloor + 1$. we'll greedily choose the best nodes with a DFS. ```cpp= #include "bits/stdc++.h" using namespace std; using ll = long long; #define all(x) begin(x), end(x) #define rall(x) x.rbegin(), x.rend() #define sz(x) (int)(x.size()) #define inf 1000000010 #define linf 0x3f3f3f3f3f3f3f3f #ifdef LOCAL // #define endl "\n" #include "/mnt/c/yukon/dbg.cpp" #else #define dbg(...) 0 #endif #define int ll #define endl "\n" template <typename T> ostream& operator<<(ostream& os, const vector<T>& v) { for (int i = 0; i < sz(v); ++i) { os << v[i] << ' '; } return os; } signed main() { cin.tie(0)->sync_with_stdio(0); int tc; cin >> tc; for (int _ = 0; _ < tc; _++) { int n, k; cin >> n >> k; vector<vector<int>> adj(n); for (int i = 1; i < n; i++) { int x; cin >> x; --x; adj[x].push_back(i); } vector<int> score(n); for (int i = 0; i < n; i++) { cin >> score[i]; } // each part of adj[0] naturally gets k / sz(adj[0]), and we'll add remainders greedily map<int, int> dp; const int ct = 1e9 + 7; function<int(int u, int rem)> dfs = [&](int u, int rem) { int key = u * ct + rem; if (dp.count(key)) return dp[key]; int gain = 0; if (sz(adj[u])) { int split = rem / sz(adj[u]); int ext = rem % sz(adj[u]); vector<int> could; for (int i = 0; i < sz(adj[u]); i++) { int prev = dfs(adj[u][i], split); gain += prev; if (ext) could.push_back(dfs(adj[u][i], split + 1) - prev); } if (ext) sort(all(could)); while (ext) { gain += could.back(); could.pop_back(); ext--; } // dbg(split); } return dp[key] = gain + (score[u] * rem); }; ll ans = dfs(0, k); cout << ans << endl; #ifdef LOCAL cout << "____________________" << endl; #endif } } // don't get stuck on one approach // question bounds // flesh out your approach before implementing o.o ``` ## what's a segment tree? a segment tree essentially lets you do point update and range query in $\mathcal{O}(n \log n)$ for **any associative** operation. so... that means, we can update any point in an our segment tree (or essentially an index) in $\mathcal{O}(\log n)$, slower than a standard array. BUT, we can query a range in $\log n$, instead of $n$. this works because we use a "divide-and-conquer" approach when aswering these queries. $f(n) = f(0,\frac{n}{2}) + (\text{operation}) + f(\frac{n}{2} + 1, n)$. for example, let's say we want to use the associative operation of addition on the array: $$\{1, 2, -2, -1\}$$ ![](https://i.imgur.com/yf9A0pa.png) from this, we know that the number of nodes will stay linear, because $2^{\lceil \log_2n\rceil} < 4n$ when we query a contiguous subarray of this tree, we'll keep searching down the tree until we reach the targeted end points. the time complexity is $\log n$ because only the most left and right pointers can extend. In the worst case, they both "spawn" two new pointers, so at most, we visit $4$ nodes at each level. this makes the time complexity for a query $4 \log n$ ![](https://i.imgur.com/H26XXQA.png) Thus, we can query any *associative* operation in $\mathcal{O}(\log n)$ with $\mathcal{O}(\log n)$ point updates.