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