Unverified Commit 868ec7fa authored by Shuhao Zhang's avatar Shuhao Zhang Committed by GitHub
Browse files

Merge pull request #8 from 24OI/master

sync 2019.4.18
parents 64dc7f19 2fc16e67
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ script:
  - mkdocs build -v
  - find ./site -type f -name '*.html' -exec node --max_old_space_size=512 ./scripts/render_math.js {} \;
  - npx gulp minify
  - docker build -t oi-wiki:latest .
  - set +e
deploy:
  provider: pages
+16 −18
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@

在二分搜索过程中,每次都把查询的区间减半,因此对于一个长度为 $n$ 的数组,至多会进行 $O(\log n)$ 次查找。

```c++
```cpp
int binary_search(int start, int end, int key) {
  int ret = -1;  // 未搜索到数据返回-1下标
  int mid;
@@ -59,15 +59,14 @@ int binary_search(int start, int end, int key) {

解题的时候往往会考虑枚举答案然后检验枚举的值是否正确。如果我们把这里的枚举换成二分,就变成了“二分答案”。

来看一看一道例题[Luogu  P1873 砍树](https://www.luogu.org/problemnew/show/P1873),我们可以 1 到 1000000000(10 亿)枚举答案,但是这种朴素写法肯定拿不到满分,因为从 1 跑到 10 亿太耗时间。我们可以对答案进行 1 到 10 亿的二分,其中,每次都对其进行检查可行性(一般都是使用贪心法)。 **这就是二分答案。** 
来看一看一道例题 [Luogu P1873 砍树](https://www.luogu.org/problemnew/show/P1873),我们可以 1 到 1000000000(10 亿)枚举答案,但是这种朴素写法肯定拿不到满分,因为从 1 跑到 10 亿太耗时间。我们可以对答案进行 1 到 10 亿的二分,其中,每次都对其进行检查可行性(一般都是使用贪心法)。 **这就是二分答案。** 

下面就是例题的参考答案。

```c++
```cpp
int a[1000005];
int n, m;
bool check(int k)  //检查可行性,k为锯片高度
{
bool check(int k) { //检查可行性,k为锯片高度
  long long sum = 0;
  for (int i = 1; i <= n; i++)       //检查每一棵树
    if (a[i] > k)                    //如果树高于锯片高度
@@ -76,8 +75,7 @@ bool check(int k) //检查可行性,k为锯片高度
}
int find(int x) {
  int l = 1, r = 1000000001;  //因为是左闭右开的,所以10亿要加1
  while (l + 1 < r)           //如果两点不相邻
  {
  while (l + 1 < r) {         //如果两点不相邻
    int mid = (l + r) / 2;  //取中间值
    if (check(mid))         //如果可行
      l = mid;              //升高锯片高度
@@ -114,29 +112,29 @@ int main() {

## 三分法

```c++
mid = left + (right - left >> 1);
midmid = mid + (right - mid >> 1);  // 对右侧区间取半
if (cal(mid) > cal(midmid))
  right = midmid;
```cpp
lmid = left + (right - left >> 1);
rmid = lmid + (right - lmid >> 1);  // 对右侧区间取半
if (cal(lmid) > cal(rmid))
  right = rmid;
else
  left = mid;
  left = lmid;
```

三分法可以用来查找凸函数的最大(小)值。

画一下图好理解一些(图待补)

-   如果 `mid``midmid` 在最大(小)值的同一侧:
-   如果 `lmid``rmid` 在最大(小)值的同一侧:
    那么由于单调性,一定是二者中较大(小)的那个离最值近一些,较远的那个点对应的区间不可能包含最值,所以可以舍弃。
-   如果在两侧:
    由于最值在二者中间,我们舍弃两侧的一个区间后,也不会影响最值,所以可以舍弃。

## 分数规划

分数规划是这样一类问题,每个物品有两个代价 $c_i$ , $d_i$ ,要求通过某种方式选出若干个,使得 $\frac{\sum{c_i}}{\sum{d_i}}$ 最大或最小。
分数规划是这样一类问题,每个物品有两个属性 $c_i$ , $d_i$ ,要求通过某种方式选出若干个,使得 $\frac{\sum{c_i}}{\sum{d_i}}$ 最大或最小。

经典的例子 最优比率环、最优比率生成树 等等。
经典的例子 最优比率环、最优比率生成树 等等。

### 二分法

@@ -170,4 +168,4 @@ $$

### Dinkelbach 算法

Dinkelbach 算法是每次用上一轮的答案当做新的 $L$ 来输入,不断地迭代,直至答案收敛。
Dinkelbach 算法的大概思想是每次用上一轮的答案当做新的 $L$ 来输入,不断地迭代,直至答案收敛。
+23 −0
Original line number Diff line number Diff line
冒泡排序是一种稳定的排序方法。

以升序为例,冒泡排序每次检查相邻两个元素,如果前面的元素大于后面的元素,就将相邻两个元素交换。当没有相邻的元素需要交换时,排序就完成了。

不难发现,我们最多需要扫描 $n$ 遍数组才能完成排序。

```cpp
void bubble_sort() {
  for (int i = 1; i <= n; i++) {
    bool flag = false;
    for (int j = 1; j < n; j++)
      if (a[j] > a[j + 1]) {
        flag = true;
        int t = a[j];
        a[j] = a[j + 1];
        a[j + 1] = t;
      }
    if (!flag) break;  //如果没有执行交换操作,说明数列已经有序
  }
}
```

在序列完全有序时,该算法只需遍历一遍数组,不用执行任何交换操作,时间复杂度为 $O(n)$ 。在最坏情况下,冒泡排序要执行 $\frac{(n-1)n}{2}$ 次交换操作,时间复杂度为 $O(n^2)$ 。在平均情况下,冒泡排序的时间复杂度也是 $O(n^2)$ 。
 No newline at end of file
+11 −0
Original line number Diff line number Diff line
计数排序也称桶排序,可以在 $O(n)$ 的时间复杂度内对数组进行排序,但是它的空间复杂度与需要排序的数组的值域相关。

但实际操作时,由于时空同阶,时间复杂度应为 $O(\max\left(n,U\right))$,其中 $U$ 代表数组元素的值域大小。

!!! warning "注"
    注意区分 **基数排序****桶排序**

算法流程就是记录每一个数出现了多少次,然后从小到大依次输出。

一般考虑的是某一范围内的整数,但是计数排序也可以和[离散化](/misc/discrete)一起使用,来对浮点数、大整数进行排序。
+46 −0
Original line number Diff line number Diff line
@@ -4,6 +4,10 @@

这要求我们在解题时,要思考问题规模增长对答案的影响,这种影响是否可以推广。(比如在设计动态规划方法的时候,要考虑从一个状态到后继状态的转移会造成什么影响)。

构造题一个很显著的特点就是高自由度,也就是说一道题的构造方式可能有很多种,但是会有一种较为简单的构造方式满足题意。看起来是放宽了要求,让题目变的简单了,但很多时候,正是这种高自由度导致题目没有明确思路而无从下手。

构造题另一个特点就是形式灵活,变化多样。并不存在一个通用解法或套路可以解决所有构造题,甚至很难找出解题思路的共性。因此,下面将列举一些例题帮助读者体会构造题的一些思想内涵,给予思路上的启发。建议大家深入思考后再查看题解,也欢迎大家参与分享有趣的构造题。

## 例题:

### Example 1
@@ -65,3 +69,45 @@ $$
和 task1 同样的思路,我们发现 $1$ 必定出现在数列的第一位,否则 $1$ 出现前后的两个前缀积必然相等;而 $n$ 必定出现在数列的最后一位,因为 $n$ 出现位置后的所有前缀积在模意义下都为 $0$。手玩几组样例以后发现,所有样例中均有一组合法解满足前缀积在模意义下为 $1,2,3,\cdots,n$,因此我们可以构造出上文所述的数列来满足这个条件。那么我们只需证明这 $n$ 个数互不相同即可。

我们发现这些数均为 $1 \cdots n-2$ 的逆元 $+1$,因此各不相同,此题得解。 

### Example 3

#### Problem

[AtCoder Grand Contest 032 B](<https://atcoder.jp/contests/agc032/tasks/agc032_b>)

#### Solution

手玩一下 $n=3,4,5$ 的情况,我们可以找到一个构造思路。

构造一个完全 $k$ 分图,保证这 $k$ 部分和相等。则每个点的 $S$  均相等,为$\dfrac{(k-1)\sum_{i=1}^{n}i}{k}$。

如果 $n$ 为偶数,那么我们可以前后两两配对,即 $\{1,n\},\{2,n-1\}\cdots$

如果 $n$ 为奇数,那么我们可以把 $n$ 单拿出来作为一组,剩余的 $n-1$ 个两两配对,即$\{n\},\{1,n-1\},\{2,n-2\}\cdots$

这样构造出的图在 $n\ge 3$ 时连通性易证,在此不加赘述。

此题得解

### Example 4

#### Problem

[BZOJ4971:[Lydsy1708月赛]记忆中的背包](<https://www.lydsy.com/JudgeOnline/problem.php?id=4971>)

#### Solution

这道题是自由度最高的构造题之一了。这就导致了没有头绪,难以入手。

首先,不难发现模数是假的。由于我们自由构造数据,我们一定可以让方案数不超过模数。

通过奇怪的方式,我们想到可以通过构造 $n$ 个 代价为 $1$ 的小物品和几个代价大于 $\dfrac{w}{2}$ 的大物品。

由于大物品只能取一件,所以每个代价为 $x$ 的大物品对方案数的贡献为 $C_{n}^{w-x}$。

令 $f_{i,j}$ 表示有 $i$ 个 $1$,方案数为 $j$ 的最小大物品数。

用 dp 预处理出 $f$ ,通过计算可知只需预处理 $i\le 20$ 的所有值即可。

此题得解
Loading