Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
27rabbitlt committed Jan 1, 2025
1 parent 1581345 commit 9bc260b
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 0 deletions.
32 changes: 32 additions & 0 deletions docs/posts/ALGO/nearest_pair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Plane Nearest Point Pair

It's a popular problem to solve: given $n$ points on a plane, how to find the minimum distance between two points?

If you are familiar with Delaunay triangulation, then you can simply calculate the Delaunay triangulation first and then iterate over every edges (the total number of edges in Delaunay Tri. is linear to the number of points).

A classical and straightforward algorithm is **divide and conquer**.


We first sort all points by x-coordinate. Then we divide all points into two even halves $S_1, S_2$ and call the recursive procedure.

Suppose we already get the min distance $h_1, h_2$ for $S_1, S_2$. Let $h = \min(h_1, h_2)$. Then clearly now we only need to consider distance between a point in $S_1$ and a point in $S_2$, and both of them lie in a stripe of width $h$ from the pivot element.

![](pics/nearest_points_oiwiki.png)

Suppose we have some magics to sort the green points in y-coordinate within linear time, then we can simply use a sliding window to tract for each point in green set, which points can be candidate of nearest pair.

Another concern would be: what if the size of sliding windows is too large?

That's indeed not the problem because we can prove that there will be at most 6 points in the sliding windows otherwise the min dist in $S_1, S_2$ wouldn't be $h$.

Then how can we sort the points in y-coordinate?

Easy, we can let the recursive procedure to simply return a y-axis sorted array and for each pass we do a merge them together.

## Randomized Algorithm

There is also an interesting interative algorithm works in expected $O(n)$ time with randomization.

Suppose we already know the min dist for first $i$ points is $h$. Then we divide the whole plane into meshes of size $h$, and for each point we determine which grid it's in and store this information with a hashmap. Each time a point $v$ comes, we find the resident grid of $v$ and all candidates for new min dist must live in the surrounding grids. If no new min dist appear, then nothing happens, the algo goes on; if new min dist found, then we divide the plane into new meshes of size updated min dist $h'$.

For $i$ points, the probability that min dist involves $i$-th point is $O(\frac{1}{i})$, while the time cost to re-construct the new meshes is $O(i)$, so the expected time cost for each iteration is $O(1)$, which sum up to $O(n)$.
Binary file added docs/posts/ALGO/pics/nearest_points_oiwiki.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/posts/ALGO/pics/scanning_line_oiwiki.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions docs/posts/ALGO/scanning_line.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Scanning Line

Scanning line is an algorithm that can be used to solve problems like: calc the area of several probably overlapping rectangles.

Here is a perfect illustration:

![](pics/scanning_line_oiwiki.png)

As the figure shows, we divide the area into several sub-rectangles by imagining a scanning line moving from bottom to top.

Clearly, we need to manintain at each time point, how **wide** can the rectangle be. This can be done with a value segment tree.

What interface should the value segment tree provide?

1. Range +1/-1
2. Calculate number of all non-zero position.

Since we don't need general range query, we don't have to maintain tag pushdown. Also, since we always do a $+1$ to an interval before we do the $-1$, so we don't need to worry that the tag would be decreased to negative numbers.

In this case, segment tree is more like a data structure that divide the entire intervals into several sub-intervals and use a node to represent each interval: $[0, n], [0, n/2], [n/2, n], [0, n/4], \cdots $.

Even though we have lots of sub-intervals, there are only linear number in total.

The wonderful properpy is that for any interval $[l, r]$, we can divide it into no more than $O(\log (r - l))$ sub-intervals or i.e. nodes in segment tree.

In fact, similarly we can also divide the range $[0, n]$ into $\sqrt{n}$ sub-intervals. The idea is basically the same. s
82 changes: 82 additions & 0 deletions docs/posts/ALGO/segment_tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Segment Tree

Basic segment tree is quite easy to understand.

## ZKW Segment Tree

ZKW segment tree views the tree as a heap-like structure.

We always assume there are $n = 2^p$ for simplicity.

In segment tree, the leaf node corresponding to $a[i]$ is $i + n$, where $n$ is the size of array $a$. Thus it's quite straightforward to modify/query on a single position.

What about range query?

Suppose we want to query interval $[l, r]$, then we set two sentinels $s_l, s_r$ to be $l-1$ and $r + 1$.

Move upward in segment tree from $s_l$ and $s_r$ and check:

+ if $s_l$ is the left child, then take into account the value of the right child (the sibling of $s_l$);
+ if $s_r$ is the right child, then take into account the value of the left child (the sibling of $s_r$);

Here moving upward in segment tree means:

```C++
s_l = s_l / 2;
s_r = s_r / 2;
```

If we further consider range modify, we still need a **tag**. But unlike classical segment tree, we don't need to pushdown these tags. During query, we move upward, and at this time we naturally add up all the tags along the way up to root.

So the implementation could look like this:

```C++

void update_add(int l, int r, ll k) {

l = P + l - 1;
r = P + r + 1; // 哨兵位置
int siz = 1; // 记录当前子树大小

while (l ^ 1 ^ r) { // 当l与r互为兄弟时,只有最后一位不同
if (~l & 1)
tr[l ^ 1] += siz * k, sum[l ^ 1] += k;
if (r & 1)
tr[r ^ 1] += siz * k, sum[r ^ 1] += k;
// 类似递归线段树 tr[p] += tag[p]*(r-l+1)
l >>= 1;
r >>= 1;
siz <<= 1;
// 每次向上走时子树大小都会增加一倍
tr[l] = tr[l << 1] + tr[l << 1 | 1] + sum[l] * siz; // 维护父子关系
tr[r] = tr[r << 1] + tr[r << 1 | 1] + sum[r] * siz;
}
for (l >>= 1, siz <<= 1; l; l >>= 1, siz <<= 1)
tr[l] = tr[l << 1] + tr[l << 1 | 1] + sum[l] * siz; // 更新上传至根节点
}

ll query_sum(int l, int r) {
l = l + P - 1;
r = r + P + 1;
ll res = 0;
int sizl = 0, sizr = 0, siz = 1; // 分别维护左右两侧子树大小

while (l ^ 1 ^ r) {
if (~l & 1)
res += tr[l ^ 1], sizl += siz; // 更新答案及子树大小
if (r & 2)
res += tr[r ^ 1], sizr += siz;
l >>= 1;
r >>= 1;
siz <<= 1;

res += sum[l] * sizl + sum[r] * sizr;
// 即使当前节点所存的区间和不需要用,但因为其是两个哨兵的父亲节点,且 tag
// 不会下传, 所以其 tag 会对答案有贡献,所以需要加上 tag 的贡献
}
for (l >>= 1, sizl += sizr; l; l >>= 1)
res += sum[l] * sizl; // 累加至根节点
return res;
}

```
46 changes: 46 additions & 0 deletions test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
void update_add(int l, int r, ll k) {

l = P + l - 1;
r = P + r + 1; // 哨兵位置
int siz = 1; // 记录当前子树大小

while (l ^ 1 ^ r) { // 当l与r互为兄弟时,只有最后一位不同
if (~l & 1)
tr[l ^ 1] += siz * k, sum[l ^ 1] += k;
if (r & 1)
tr[r ^ 1] += siz * k, sum[r ^ 1] += k;
// 类似递归线段树 tr[p] += tag[p]*(r-l+1)
l >>= 1;
r >>= 1;
siz <<= 1;
// 每次向上走时子树大小都会增加一倍
tr[l] = tr[l << 1] + tr[l << 1 | 1] + sum[l] * siz; // 维护父子关系
tr[r] = tr[r << 1] + tr[r << 1 | 1] + sum[r] * siz;
}
for (l >>= 1, siz <<= 1; l; l >>= 1, siz <<= 1)
tr[l] = tr[l << 1] + tr[l << 1 | 1] + sum[l] * siz; // 更新上传至根节点
}

ll query_sum(int l, int r) {
l = l + P - 1;
r = r + P + 1;
ll res = 0;
int sizl = 0, sizr = 0, siz = 1; // 分别维护左右两侧子树大小

while (l ^ 1 ^ r) {
if (~l & 1)
res += tr[l ^ 1], sizl += siz; // 更新答案及子树大小
if (r & 1)
res += tr[r ^ 1], sizr += siz;
l >>= 1;
r >>= 1;
siz <<= 1;

res += sum[l] * sizl + sum[r] * sizr;
// 即使当前节点所存的区间和不需要用,但因为其是两个哨兵的父亲节点,且 tag
// 不会下传, 所以其 tag 会对答案有贡献,所以需要加上 tag 的贡献
}
for (l >>= 1, sizl += sizr; l; l >>= 1)
res += sum[l] * sizl; // 累加至根节点
return res;
}

0 comments on commit 9bc260b

Please sign in to comment.