diff --git a/content/5_Gold/DSU.mdx b/content/5_Gold/DSU.mdx
index a11cb83..0d7ba35 100644
--- a/content/5_Gold/DSU.mdx
+++ b/content/5_Gold/DSU.mdx
@@ -18,7 +18,6 @@ export const metadata = {
general: [
new Problem("Gold", "Mootube", "789", "Easy", false, [], "same as [CSES Road Construction](https://cses.fi/problemset/task/1676)"),
new Problem("Gold", "Closing the Farm", "646", "Easy", false, [], "similar to [CSES Network Breakdown](https://cses.fi/problemset/task/1677)"),
- new Problem("Gold", "Favorite Colors", "1042", "Hard", false, [], ""),
],
rollback: [
new Problem("YS", "Persistent Union Find", "persistent_unionfind", "Normal", false, [], ""),
diff --git a/content/6_Plat/BCC_2CC.mdx b/content/6_Plat/BCC_2CC.mdx
index b69fe81..1617b22 100644
--- a/content/6_Plat/BCC_2CC.mdx
+++ b/content/6_Plat/BCC_2CC.mdx
@@ -17,6 +17,7 @@ export const metadata = {
],
probs2: [
new Problem("CSA", "One-Way Streets", "one-way-streets", "Easy", false, [], ""),
+ new Problem("Plat", "Disruption", "842", "Normal", false, ["Merging"]),
],
bccSam: [
new Problem("CSES", "Forbidden Cities", "1705", "Normal", false, [], ""),
@@ -41,6 +42,58 @@ export const metadata = {
(implementation)
+### With DSU
+
+The analysis for USACO Platinum Disruption mentions a $O(m\alpha(n))$ solution. Although this is not a two-connected component problem, we can in fact use DSU to generate two-connected components.
+
+(explanation?)
+
+
+
+The DSU operations take $O(\log n)$ rather than $O(\alpha(n))$ because the DSU does not use union by size, but it's easy to change this.
+
+```cpp
+struct TwoEdgeCC {
+ struct {
+ vi e; void init(int n) { e = vi(n,-1); }
+ int get(int x) { return e[x] < 0 ? x : e[x] = get(e[x]); }
+ bool unite(int x, int y) { // set par[y] = x
+ x = get(x), y = get(y); if (x == y) return 0;
+ e[x] += e[y]; e[y] = x; return 1;
+ }
+ } DSU;
+ int N; vector adj; vi depth, par;
+ vpi extra;
+ void init(int _N) {
+ N = _N; DSU.init(N);
+ adj.rsz(N), depth.rsz(N), par = vi(N,-1);
+ }
+ void dfs(int x) {
+ trav(t,adj[x]) if (t != par[x])
+ par[t] = x, depth[t] = depth[x]+1, dfs(t);
+ }
+ void ae(int a, int b) {
+ if (DSU.unite(a,b)) adj[a].pb(b), adj[b].pb(a); // tree edge
+ else extra.pb({a,b});
+ }
+ void ad(int a, int b) { // OK
+ while (1) {
+ a = DSU.get(a), b = DSU.get(b);
+ if (a == b) return;
+ if (depth[a] < depth[b]) swap(a,b);
+ assert(par[a] != -1 && DSU.unite(par[a],a));
+ }
+ }
+ void gen() {
+ F0R(i,N) if (par[i] == -1) dfs(i);
+ DSU.init(N); // again!
+ trav(t,extra) ad(t.f,t.s);
+ }
+};
+```
+
+
+
### Problems
diff --git a/content/6_Plat/Merging.mdx b/content/6_Plat/Merging.mdx
index b81a5e4..59ad0ca 100644
--- a/content/6_Plat/Merging.mdx
+++ b/content/6_Plat/Merging.mdx
@@ -15,19 +15,22 @@ export const metadata = {
problems: {
sam: [
new Problem("CSES", "Distinct Colors", "1139", "Intro", false, ["Merging"]),
- ],
+ ],
general: [
new Problem("CF", "Lomsat gelral", "contest/600/problem/E", "Normal", false, ["Merging"]),
new Problem("Plat", "Promotion Counting", "696", "Normal", false, ["Merging", "Indexed Set"], ""),
- new Problem("Gold", "Favorite Colors", "1042", "Hard", false, ["DSU"], "Small to large merging is mentioned in the editorial, but we were unable to break solutions that just merged naively."),
- new Problem("Plat", "Disruption", "842", "Hard", false, ["Merging"]),
+ new Problem("Plat", "Disruption", "842", "Normal", false, ["Merging"]),
+ new Problem("POI", "Tree Rotations", "https://szkopul.edu.pl/problemset/problem/sUe3qzxBtasek-RAWmZaxY_p/site/?key=statement", "Normal", false, ["Merging", "Indexed Set"], ""),
+ new Problem("Gold", "Favorite Colors", "1042", "Hard", false, ["DSU"], "Small to large merging is mentioned in the editorial, but we were unable to break solutions that just merged naively. Alternatively, just merge linked lists in $O(1)$ time."),
+ ],
+ treeRot: [
+ new Problem("POI", "Tree Rotations 2", "https://szkopul.edu.pl/problemset/problem/b0BM0al2crQBt6zovEtJfOc6/site/?key=statement", "Very Hard", false, [], ""),
]
}
};
## Additional Reading
-
@@ -50,7 +53,7 @@ if(a.size() < b.size()){ //for two sets a and b
}
```
-By merging the smaller set into the larger one, the runtime complexity becomes $O(N\log N).$ ?? (Ben - this is false)
+By merging the smaller set into the larger one, the runtime complexity becomes $O(N\log^2N)$.
### Proof
@@ -118,3 +121,67 @@ Prove that if you instead merge sets that have size equal to the depths of the s
## Problems
+
+
+
+```cpp
+#include
+#include
+#include
+
+using namespace std;
+using namespace __gnu_pbds;
+
+template using Tree = tree,rb_tree_tag,tree_order_statistics_node_update>;
+
+const int MX = 1e5+5;
+#define sz(x) (int)(x).size()
+
+int N, a[MX], ind[MX], ans[MX], ret;
+vector child[MX];
+Tree d[MX];
+
+void comb(int a, int b) {
+ if (sz(d[a]) < sz(d[b])) d[a].swap(d[b]);
+ for (int i: d[b]) d[a].insert(i);
+}
+
+void dfs(int x) {
+ ind[x] = x;
+ for (int i: child[x]) {
+ dfs(i);
+ comb(x,i);
+ }
+ ans[x] = sz(d[x])-d[x].order_of_key(a[x]);
+ d[x].insert(a[x]);
+}
+
+int main() {
+ freopen("promote.in","r",stdin);
+ freopen("promote.out","w",stdout);
+ cin >> N; for (int i = 1; i <= N; ++i) cin >> a[i];
+ for (int i = 2; i <= N; ++i) {
+ int p; cin >> p;
+ child[p].push_back(i);
+ }
+ dfs(1);
+ for (int i = 1; i <= N; ++i) cout << ans[i] << "\n";
+}
+```
+
+
+
+(also: same solution w/o indexed set)
+
+## Faster Merging (Optional)
+
+It's easy to merge two sets of sizes $n\ge m$ in $O(n+m)$ or $(m\log n)$ time, but sometimes $O\left(m\log \frac{n}{m}\right)$ can be significantly better than both of these.
+
+
+
+
+
+
+Requires knowledge of BSTs such as treaps or splay trees.
+
+
\ No newline at end of file
diff --git a/content/6_Plat/Slope.mdx b/content/6_Plat/Slope.mdx
index 7cca6ae..78efb02 100644
--- a/content/6_Plat/Slope.mdx
+++ b/content/6_Plat/Slope.mdx
@@ -4,7 +4,7 @@ title: "Slope Trick"
author: Benjamin Qi
prerequisites:
- Platinum - Convex Hull
-description: Ways to manipulate piecewise linear convex functions.
+description: "Slope trick refers to a way to manipulate piecewise linear convex functions. Includes a simple solution to USACO Landscaping."
frequency: 1
---
@@ -12,9 +12,18 @@ import { Problem } from "../models";
export const metadata = {
problems: {
+ buy: [
+ new Problem("CF", "Buy Low Sell High", "contest/866/problem/D", "Easy", false, ["Slope Trick"], ""),
+ ],
+ potatoes: [
+ new Problem("ojuz", "LMIO - Potatoes", "LMIO19_bulves", "Normal", false, ["Slope Trick"], "[Equivalent Problem](https://atcoder.jp/contests/kupc2016/tasks/kupc2016_h)"),
+ ],
+ landscaping: [
+ new Problem("Plat", "Landscaping", "650", "Hard", false, ["Slope Trick"], "Equivalent Problem: GP of Wroclaw 20 J"),
+ ],
general: [
- new Problem("CF", "Bookface", "gym/102576/problem/C", "Easy", false, ["Slope Trick"], ""),
- new Problem("CC", "CCDSAP Exam", "CCDSAP", "Easy", false, ["Slope Trick"], ""),
+ new Problem("CF", "Bookface", "gym/102576/problem/C", "Normal", false, ["Slope Trick"], ""),
+ new Problem("CC", "CCDSAP Exam", "CCDSAP", "Normal", false, ["Slope Trick"], ""),
new Problem("CF", "Farm of Monsters", "gym/102538/problem/F", "Hard", false, ["Slope Trick"], ""),
new Problem("CF", "Moving Walkways", "contest/1209/problem/H", "Hard", false, ["Slope Trick"], ""),
new Problem("CF", "April Fools' Problem", "contest/802/problem/O", "Very Hard", false, ["Slope Trick"], "binary search on top of slope trick"),
@@ -23,8 +32,6 @@ export const metadata = {
}
};
-**Slope trick** refers to manipulating piecewise linear convex functions. Includes a simple solution to [Landscaping](http://www.usaco.org/index.php?page=viewproblem2&cpid=650).
-
## Tutorials
@@ -39,27 +46,140 @@ From the latter link (modified):
> - It can be divided into multiple sections, where each section is a linear function (usually) with an integer slope.
> - It is a convex/concave function. In other words, the slope of each section is non-decreasing or non-increasing when scanning the function from left to right.
-It's generally applicable as a DP optimization. Usually you can come up with a slower DP (ex. $O(N^2)$) first and then optimize it to $O(N\log N)$ with slope trick. The rest of this module assumes that you are somewhat familiar with at least one of the tutorials mentioned above.
+It's generally applicable as a DP optimization. The rest of this module assumes that you are somewhat familiar with at least one of the tutorials mentioned above.
-## [Buy Low Sell High](https://codeforces.com/contest/866/problem/D)
+
-**Slow Solution**: Let $dp[i][j]$ denote the maximum amount of money you can have on day $i$ if you have exactly $j$ shares of stock on that day. The final answer will be $dp[N][0]$. This easily leads to an $O(N^2)$ DP.
+Usually you can come up with a slower (usually $O(N^2)$) DP first and then optimize it to $O(N\log N)$ with slope trick.
-Of course, we never used the fact that the DP is concave down! Specifically, let $dif[i][j]=dp[i][j]-dp[i][j+1]\ge 0$. Then $dif[i][j]\le dif[i][j+1]$ for all $j\ge 0$ (ignoring the case when we get $dp$ values of $-\infty$).
+
-We'll process the shares in order. Suppose that on the current day shares are worth $p$. We can replace (buy or sell a share) in the statement with (buy, then sell between 0 and 2 shares).
+## Buy Low Sell High
- * If we currently have $j$ shares and overall balance $b$, then after buying, $j$ increases by one and $b$ decreases by $p$. The differences between every two consecutive elements do not change.
- * If we choose to buy a share, this is equivalent to setting $dp[i][j]=\max(dp[i][j],dp[i][j+1]+p)$ for all $j$. By the concavity condition, $dp[i][j]=dp[i][j+1]+p$ will hold for all $j$ less than a certain threshold while $dp[i][j+1]$ will hold for all others. So this is equivalent to inserting $p$ into the list of differences while maintaining the condition that the differences are in sorted order.
- * So we add $p$ to the list of differences two times. After that, we should pop the smallest difference in the list because we can't end up with a negative amount of shares.
+
-(insert diagram)
+### Slow Solution
-(insert example)
+Let $dp[i][j]$ denote the maximum amount of money you can have on day $i$ if you have exactly $j$ shares of stock on that day. The final answer will be $dp[N][0]$. This solution runs in $O(N^2)$ time.
-The implementation is quite simple; maintain a priority queue that allows you to pop the minimum element.
+
-
+```cpp
+
+vector dp = {{0}};
+int N;
+
+int main() {
+ re(N);
+ F0R(i,N) {
+ int x; re(x);
+ dp.pb(vl(i+2,-INF));
+ F0R(j,i+1) {
+ ckmax(dp.bk[j+1],dp[sz(dp)-2][j]-x);
+ ckmax(dp.bk[j],dp[sz(dp)-2][j]);
+ if (j) ckmax(dp.bk[j-1],dp[sz(dp)-2][j]+x);
+ }
+ }
+ int cnt = 0;
+ trav(t,dp) {
+ pr("dp[",cnt++,"] = ");
+ pr('{');
+ F0R(i,sz(t)) {
+ if (i) cout << ", ";
+ cout << setw(3) << t[i];
+ }
+ ps('}');
+ }
+}
+```
+
+
+
+If we run this on the first sample case, then we get the following table:
+
+```
+Input:
+
+9
+10 5 4 7 9 12 6 2 10
+
+Output:
+
+dp[0] = { 0}
+dp[1] = { 0, -10}
+dp[2] = { 0, -5, -15}
+dp[3] = { 0, -4, -9, -19}
+dp[4] = { 3, -2, -9, -16, -26}
+dp[5] = { 7, 0, -7, -16, -25, -35}
+dp[6] = { 12, 5, -4, -13, -23, -35, -47}
+dp[7] = { 12, 6, -1, -10, -19, -29, -41, -53}
+dp[8] = { 12, 10, 4, -3, -12, -21, -31, -43, -55}
+dp[9] = { 20, 14, 7, -2, -11, -21, -31, -41, -53, -65}
+```
+
+However, the DP values look quite special! Specifically, let
+$$
+dif[i][j]=dp[i][j]-dp[i][j+1]\ge 0.
+$$
+Then $dif[i][j]\le dif[i][j+1]$ for all $j\ge 0$. In other words, $dp[i][j]$ as a function of $j$ is **concave down**.
+
+### Full Solution
+
+
+
+We'll process the shares in order. Suppose that we are currently considering the $i$-th day, where shares are worth $p_i$. We can replace (buy or sell a share) in the statement with (buy, then sell somewhere between 0 and 2 shares).
+
+ - If we currently have $j$ shares and overall balance $b$, then after buying, $j$ increases by one and $b$ decreases by $p.$ So we set $dp[i][j]=dp[i-1][j-1]-p$ for all $j$. Note that the differences between every two consecutive elements of $dp[i]$ have not changed.
+
+ - If we choose to sell a share, this is equivalent to setting $dp[i][j]=\max(dp[i][j],dp[i][j+1]+p)$ for all $j$ at the same time. By the concavity condition, $dp[i][j]=dp[i][j+1]+p$ will hold for all $j$ less than a certain threshold while $dp[i][j]$ will remain unchanged for all others. So this is equivalent to inserting $p$ into the list of differences while maintaining the condition that the differences are in sorted order.
+
+ - So choosing to sell between 0 and 2 shares is represented by adding $p$ to the list of differences two times. After that, we should pop the smallest difference in the list because we can't end up with a negative amount of shares.
+
+**Example:** consider the transition from `dp[4]` to `dp[5]`. Note that $p_5=9$.
+
+Start with:
+
+```
+dp[4] = { 3, -2, -9, -16, -26}
+dif[4] = { 5, 7, 7, 10}
+```
+
+
+
+After buying one share, $9$ is subtracted from each value and they are shifted one index to the right.
+
+```
+dp[5] = { x, -6, -11, -18, -25, -35}
+dif[5] = { x, 5, 7, 7, 10}
+```
+
+
+
+Then we can choose to sell one share at price $9$. The last two DP values remain the same while the others change.
+
+```
+dp[5] = { 3, -2, -9, -16, -25, -35}
+dif[5] = { 5, 7, 7, 9, 10}
+```
+
+
+
+Again, we can choose to sell one share at price $9$. The last three DP values remain the same while the others change.
+
+```
+dp[5] = { 7, 0, -7, -16, -25, -35}
+dif[5] = { 7, 7, 9, 9, 10}
+```
+
+
+
+(insert diagrams)
+
+
+
+
+
+The implementation is quite simple; maintain a priority queue representing $dif[i]$ that allows you to pop the minimum element. After adding $i$ elements, $ans$ stores the current value of $dp[i][i]$. At the end, you add all the differences in $dif[N]$ to go from $dp[N][N]$ to $dp[N][0]$.
```cpp
#include
@@ -80,28 +200,39 @@ int main() {
cout << ans << "\n";
}
```
+
### Extension
*Stock Trading (USACO Camp)*: What if your amount of shares can go negative, but you can never have more than $L$ shares or less than $-L$?
-## [Potatoes](https://oj.uz/problem/view/LMIO19_bulves)
+## Potatoes
-[Equivalent Problem](https://atcoder.jp/contests/kupc2016/tasks/kupc2016_h)
+
-Let $dif_i=a_i-b_i$. Defining $d_j=\sum_{i=1}^jdif_i$, our goal is to move around the potatoes such that $d_0,d_1,\ldots,d_N$ is a non-decreasing sequence. Moving a potato is equivalent to changing exactly one of the $d_i$ (aside from $d_0,d_N$) by one.
+### Simplifying the Problem
-**Slow Solution:** Let $dp[i][j]$ be the minimum cost to determine $d_0,d_1,\ldots,d_i$ such that $d_i\le j$ for each $0\le j\le d_N$. This gives a $O(N\cdot d_N)$ solution.
+Let $dif_i=a_i-b_i$. Defining $d_j=\sum_{i=1}^jdif_i$, our goal is to move around the potatoes such that $d_0,d_1,\ldots,d_N$ is a non-decreasing sequence. Moving a potato one position is equivalent to changing exactly one of the $d_i$ by one (although $d_0,d_N$ cannot be modified).
-As before, this DP is concave up for a fixed $i$! Given a piecewise linear function $DP_x$, we need to support the following operations.
+### Slow Solution
+
+Let $dp[i][j]$ be the minimum cost to determine $d_0,d_1,\ldots,d_i$ such that $d_i\le j$ for each $0\le j\le d_N$. This runs in $O(N\cdot d_N)$ time. By definition, $dp[i][j]\ge dp[i][j+1]$.
+
+### Full Solution
+
+
+
+Similar to before, this DP is concave up for a fixed $i$! Given a piecewise linear function $DP_x$, we need to support the following operations.
* Add $|x-k|$ to the function for some $k$
* Set $DP_x=\min(DP_x,DP_{x-1})$ for all $x$
-Again, these can be done with a priority queue in $O(N\log N)$ time!
+Again, these can be done with a priority queue. Instead of storing the consecutive differences, we store the points where the slope of the piecewise linear function changes in $O(N\log N)$ time.
-
+
+
+
```cpp
#include
@@ -134,22 +265,29 @@ int main() {
cout << fst << "\n";
}
```
+
-## [Landscaping](http://www.usaco.org/index.php?page=viewproblem2&cpid=650)
+## USACO Landscaping
-Equivalent Problem: GP of Wroclaw 20 J
+
-This is quite similar to the previous task, so it's easy to guess that slope trick is applicable.
+This looks quite similar to the previous task, so it's not hard to guess that slope trick is applicable.
-Again, let's first come up with a slow DP. Let $dp[i][j]$ equal the number of ways to move dirt around the first $i$ flowerbeds such that the first $i-1$ flowerbeds all have the correct amount of dirt while the $i$-th flowerbed has $j$ extra units of dirt (or lacks $-j$ units of dirt if $j$ is negative). The answer will be $dp[N][0]$.
+### Slow Solution
+
+Let $dp[i][j]$ equal the number of ways to move dirt around the first $i$ flowerbeds such that the first $i-1$ flowerbeds all have the correct amount of dirt while the $i$-th flowerbed has $j$ extra units of dirt (or lacks $-j$ units of dirt if $j$ is negative). The answer will be $dp[N][0]$.
+
+### Full Solution
+
+
This DP is concave up for any fixed $i$. To get $dp[i+1]$ from $dp[i]$ we must be able to support the following operations.
- * Shift the DP curve $A_i$ units to the right.
- * Shift the DP curve $B_i$ units to the left.
- * Add $Z\cdot |j|$ to $DP[j]$ for all $j$.
- * Set $DP[j] = \min(DP[j],DP[j-1]+X)$ and $DP[j] = \min(DP[j],DP[j+1]+Y)$ for all $j$.
+ - Shift the DP curve $A_i$ units to the right.
+ - Shift the DP curve $B_i$ units to the left.
+ - Add $Z\cdot |j|$ to $DP[j]$ for all $j$.
+ - Set $DP[j] = \min(DP[j],DP[j-1]+X)$ and $DP[j] = \min(DP[j],DP[j+1]+Y)$ for all $j$.
As before, it helps to look at the differences $dif[j]=DP[j+1]-dif[j]$ instead. Then the last operation is equivalent to the following:
@@ -158,7 +296,7 @@ As before, it helps to look at the differences $dif[j]=DP[j+1]-dif[j]$ instead.
If we maintain separate deques for $dif$ depending on whether $j\ge 0$ or $j<0$ and update all of the differences in the deques "lazily" then we can do this in $O(\sum A_i+\sum B_i)$ time.
-Bonus: Solve this problem when $\sum A_i+\sum B_i$ is not so small.
+
@@ -201,6 +339,10 @@ int main() {
```
+### Extension
+
+We can solve this problem when $\sum A_i+\sum B_i$ is not so small with lazy balanced binary search trees.
+
## Problems
\ No newline at end of file
diff --git a/content/7_Advanced/Treaps.mdx b/content/7_Advanced/Treaps.mdx
index 598a66c..90c7cee 100644
--- a/content/7_Advanced/Treaps.mdx
+++ b/content/7_Advanced/Treaps.mdx
@@ -29,12 +29,4 @@ IOI 2013 Game - https://oj.uz/submission/242393
A2OJ: https://a2oj.com/category?ID=14
- ?
-
-## Splitting & Merging Segment Trees
-
-### Tutorial
-
- - [Merging Segment Trees](https://codeforces.com/blog/entry/49446)
- - [Tree Rotations 2](https://szkopul.edu.pl/problemset/problem/b0BM0al2crQBt6zovEtJfOc6/site/?key=statement) [](193)
-
+ ?
\ No newline at end of file