Unverified Commit ea70437a authored by Xeonacid's avatar Xeonacid Committed by GitHub
Browse files

Merge branch 'master' into patch-1

parents feae47a7 c107d4a1
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -122,9 +122,10 @@
 **Lemma 9** :考虑三个点 $u,v,w$ 满足 $\alpha(u)<\alpha(v)<\alpha(w)$ ,如果 $uw$ 相连, $vw$ 不相连,则 $w$ 只给 $u$ 的 $label$ 贡献,不给 $v$ 贡献。为了让 $v$ 比 $u$ 先加入序列,需要一个 $x$ 满足 $\alpha(v)<\alpha(x)$ 且 $vx$ 相连, $ux$ 不相连,即 $x$ 只给 $v$ 贡献而不给 $u$ 贡献。

 **Lemma 10** :任意一个弦图一定不存在一个序列 $v_0,v_1,...,v_k(k\ge 2)$ 满足下列性质:
1\. $v_iv_j$ 相连当且仅当 $|i-j|=1$ 。
1\. $\alpha(v_0)>\alpha(v_i)(i\in[1,k])$ 。
1\. 存在 $i\in[1,k-1]$ ,满足 $\alpha(v_i)<\alpha(v_{i+1})<...<\alpha(v_k)$ 且 $\alpha(v_i)<\alpha(v_{i-1})<...<\alpha(v_1)<\alpha(v_k)<\alpha(v_0)$ 。

1.  $v_iv_j$ 相连当且仅当 $|i-j|=1$ 。
2.  $\alpha(v_0)>\alpha(v_i)(i\in[1,k])$ 。
3. 存在 $i\in[1,k-1]$ ,满足 $\alpha(v_i)<\alpha(v_{i+1})<...<\alpha(v_k)$ 且 $\alpha(v_i)<\alpha(v_{i-1})<...<\alpha(v_1)<\alpha(v_k)<\alpha(v_0)$ 。

证明:

+27 −1
Original line number Diff line number Diff line
@@ -131,9 +131,35 @@ Dinic 算法有两个优化:
1.  **多路增广** :每次找到一条增广路的时候,如果残余流量没有用完怎么办呢?我们可以利用残余部分流量,再找出一条增广路。这样就可以在一次 DFS 中找出多条增广路,大大提高了算法的效率。
2.  **当前弧优化** :如果一条边已经被增广过,那么它就没有可能被增广第二次。那么,我们下一次进行增广的时候,就可以不必再走那些已经被增广过的边。

#### 时间复杂度

设点数为 $n$ ,边数为 $m$ ,那么 Dinic 算法的时间复杂度(在应用上面两个优化的前提下)是 $O(n^{2}m)$ ,在稀疏图上效率和 EK 算法相当,但在稠密图上效率要比 EK 算法高很多。

特别地,在求解二分图最大匹配问题时,可以证明 Dinic 算法的时间复杂度是 $O(m\sqrt{n})$ 。
首先考虑单轮增广的过程。在应用了 **当前弧优化** 的前提下,对于每个点,我们维护下一条可以增广的边,而当前弧最多变化 $m$ 次,从而单轮增广的最坏时间复杂度为 $O(nm)$ 。

接下来我们证明,最多只需 $n-1$ 轮增广即可得到最大流。

我们先回顾下 Dinic 的增广过程。对于每个点,Dinic 只会找比该点层数多 $1$ 的点进行增广。

首先容易发现,对于图上的每个点,一轮增广后其层数一定不会减小。而对于汇点 $t$ ,情况会特殊一些,其层数在一轮增广后一定增大。

对于后者,我们考虑用反证法证明。如果 $t$ 的层数在一轮增广后不变,则意味着在上一次增广中,仍然存在着一条从 $s$ 到 $t$ 的增广路,且该增广路上相邻两点间的层数差为 $1$ 。这条增广路应该在上一次增广过程中就被增广了,这就出现了矛盾。

从而我们证明了汇点的层数在一轮增广后一定增大,即增广过程最多进行 $n-1$ 次。

综上 Dinic 的最坏时间复杂度为 $O(n^{2}m)$ 。事实上在一般的网络上,Dinic 算法往往达不到这个上界。

特别地,在求解二分图最大匹配问题时,Dinic 算法的时间复杂度是 $O(m\sqrt{n})$ 。接下来我们将给出证明。

首先我们来简单归纳下求解二分图最大匹配问题时,建立的网络的特点。我们发现这个网络中,所有边的流量均为 $1$ ,且除了源点和汇点外的所有点,都满足入边最多只有一条,或出边最多只有一条。我们称这样的网络为 **单位网络**

对于单位网络,一轮增广的时间复杂度为 $O(m)$ ,因为每条边只会被考虑最多一次。

接下来我们试着求出增广轮数的上界。假设我们已经先完成了前 $\sqrt{n}$ 轮增广,因为汇点的层数在每次增广后均严格增加,因此所有长度不超过 $\sqrt{n}$ 的增广路都已经在之前的增广过程中被增广。设前 $\sqrt{n}$ 轮增广后,网络的流量为 $f$ ,而整个网络的最大流为 $f'$ ,设两者间的差值 $d=f'-f$ 。

因为网络上所有边的流量均为 $1$ ,所以我们还需要找到 $d$ 条增广路才能找到网络最大流。又因为单位网络的特点,这些增广路不会在源点和汇点以外的点相交。因此这些增广路至少经过了 $d\sqrt{n}$ 个点(每条增广路的长度至少为 $\sqrt{n}$ ),且不能超过 $n$ 个点。因此残量网络上最多还存在 $\sqrt{n}$ 条增广路。也即最多还需增广 $\sqrt{n}$ 轮。

综上,对于包含二分图最大匹配在内的单位网络,Dinic 算法可以在 $O(m\sqrt{n})$ 的时间内求出其最大流。

??? note "参考代码"
    ```cpp
+51 −81
Original line number Diff line number Diff line
@@ -2,12 +2,12 @@

 `set` 是关联容器,含有键值类型对象的已排序集,搜索、移除和插入拥有对数复杂度。 `set` 内部通常采用红黑树实现。

和数学中的集合相似, `set` 中不会出现值相同的元素。
和数学中的集合相似, `set` 中不会出现值相同的元素。如果需要有相同元素的集合,需要使用 `multiset``multiset` 的使用方法与 `set` 的使用方法基本相同。

### 插入与删除操作

-  `insert(x)` 当容器中没有等价元素的时候,将元素 x 插入到 `set` 中。
-  `erase(x)` 删除值为 x 的元素,返回删除元素的个数。
-  `erase(x)` 删除值为 x 的 **所有** 元素,返回删除元素的个数。
-  `erase(pos)` 删除迭代器为 pos 的元素,要求迭代器必须合法。
-  `erase(first,last)` 删除迭代器在 $[first,last)$ 范围内的所有元素。
-  `clear()` 清空 `set`
@@ -15,7 +15,7 @@
???+note "insert 函数的返回值"
    insert 函数的返回值类型为 `pair<iterator, bool>` ,其中 iterator 是一个指向所插入元素(或者是指向等于所插入值的原本就在容器中的元素)的迭代器,而 bool 则代表元素是否插入成功,由于 `set` 中的元素具有唯一性质,所以如果在 `set` 中已有等值元素,则插入会失败,返回 false,否则插入成功,返回 true; `map` 中的 insert 也是如此。

### 迭代器
### 迭代器<span id="set-iterator"></span>

 `set` 提供了以下几种迭代器:

@@ -30,7 +30,7 @@

以上列出的迭代器中,含有字符 `c` 的为只读迭代器,你不能通过只读迭代器去修改 `set` 中的元素的值。如果一个 `set` 本身就是只读的,那么它的一般迭代器和只读迭代器完全等价。只读迭代器自 C++11 开始支持。

### 查找操作
### 查找操作<span id="set-find"></span>

-  `count(x)` 返回 `set` 内键为 x 的元素数量。
-  `find(x)``set` 内存在键为 x 的元素时会返回该元素的迭代器,否则返回 `end()`
@@ -44,34 +44,11 @@
    
    但使用 `algorithm` 库中的 `lower_bound``upper_bound` 函数对 `set` 中的元素进行查询,时间复杂度为 $O(n)$ 。

##  `multiset` 

 `multiset` 是关联容器,含有键值类型对象的已排序集,搜索、移除和插入拥有对数复杂度。

与 `set` 不同的是, `multiset` 允许不同元素间拥有相同的值。

### 插入与删除操作

-  `insert(x)` 将元素 x 插入到 `multiset` 中。
-  `erase(x)` 删除值为 x 的 **所有** 元素,返回删除元素的个数。
-  `erase(pos)` 删除迭代器为 pos 的元素,要求迭代器必须合法。
-  `erase(first,last)` 删除迭代器在 $[first,last)$ 范围内的所有元素。
-  `clear()` 清空 `multiset` 。

### 迭代器

 `multiset` 的迭代器和 `set` 的 [迭代器](#_2) 类似,这里不再赘述。

### 查找操作

 `multiset` 的查找操作和 `set`[查找操作](#_3) 类似,这里不再赘述。

##  `map` 

 `map` 是有序键值对(Attribute–value pair)容器,它的元素的键是唯一的。搜索、移除和插入操作拥有对数复杂度。 `map` 通常实现为红黑树。
 `map` 是有序键值对容器,它的元素的键是唯一的。搜索、移除和插入操作拥有对数复杂度。 `map` 通常实现为红黑树。

你可能需要存储一些键值对,例如存储学生姓名对应的分数: `Tom 0``Bob 100``Alan 100`
但是由于数组下标只能为非负整数,所以无法用姓名作为下标来存储,这个时候最简单的办法就是使用 STL 中的 `map` 了!
你可能需要存储一些键值对,例如存储学生姓名对应的分数: `Tom 0``Bob 100``Alan 100` 。但是由于数组下标只能为非负整数,所以无法用姓名作为下标来存储,这个时候最简单的办法就是使用 STL 中的 `map` 了!

 `map` 重载了 `operator[]` ,可以用任意定义了 `operator <` 的类型作为下标(在 `map` 中叫做 `key` ,也就是索引):

@@ -85,77 +62,70 @@ map<Key, T> yourMap;
map<string, int> mp;
```

### 添加元素
 `map` 中不会存在键相同的元素, `multimap` 中允许多个元素拥有同一键。 `multimap` 的使用方法与 `map` 的使用方法基本相同。

1. 直接赋值,例如 `mp["Tom"]=0` 
2. 通过插入一个类型为 `pair<Key, T>` 的值,例如 `mp.insert(pair<string,int>("Alan",100));` 
3. 使用 `initializer_list`
!!! warning
    正是因为 `multimap` 允许多个元素拥有同一键的特点, `multimap` 并没有提供给出键访问其对应值的方法。

```cpp
map<string, int> mp = {{"Tom", 0}, {"Bob", "100"}, {"Alan", 100}};
```
### 插入与删除操作<span id="map-insert"></span>

### 查找、修改元素
- 可以直接通过下标访问来进行查询或插入操作。例如 `mp["Alan"]=100`
- 通过向 `map` 中插入一个类型为 `pair<Key, T>` 的值可以达到插入元素的目的,例如 `mp.insert(pair<string,int>("Alan",100));`
-  `erase(key)` 函数会删除键为 `key`**所有** 元素。返回值为删除元素的数量。
-  `erase(pos)` : 删除迭代器为 pos 的元素,要求迭代器必须合法。
-  `erase(first,last)` : 删除迭代器在 $[first,last)$ 范围内的所有元素。
-  `clear()` 函数会清空整个容器。

1. 使用赋值语法: `int grade=mp["Tom"]`
2. 使用成员函数 `iterator find( const Key& key );` 来确定一个索引是否在 `map` 中。它会返回指向该元素的迭代器;如果索引不在 `map` 中,则会返回尾后迭代器 `mp.end()`
3. 如果你想获得 `map` 里全部的元素,请使用迭代器,解引用迭代器会得到一个类型为 `pair<Key, T>` 的值:
???+note "下标访问中的注意事项"
    在利用下标访问 `map` 中的某个元素时,如果 `map` 中不存在相应键的元素,会自动在 `map` 中插入一个新元素,并将其值设置为默认值(对于整数,值为零;对于有默认构造函数的类型,会调用默认构造函数进行初始化)。
    
```cpp
for (iter = mp.begin(); iter != mp.end(); ++iter)
  cout << iter->first << " " << iter->second << endl;
```
    当下标访问操作过于频繁时,容器中会出现大量无意义元素,影响 `map` 的效率。因此一般情况下推荐使用 `find()` 函数来寻找特定键的元素。

其中使用 `mp.begin()` 可以得到指向 `map` 首元素的迭代器。
如果使用 C++11(及以上),还可以使用 C++11 的范围 for 循环
### 查询操作

```cpp
for (auto &i : mp) {
  printf("Key : %d, Value : %d\n", i.first, i.second);
}
```

使用迭代器遍历大小为 $n$ 的 `map` 的时间复杂度是 $O(n)$ 。
-  `count(x)` : 返回容器内键为 x 的元素数量。复杂度为 $O(\log(size)+ans)$ (关于容器大小对数复杂度,加上匹配个数)。
-  `find(x)` : 若容器内存在键为 x 的元素,会返回该元素的迭代器;否则返回 `end()` 。
-  `lower_bound(x)` : 返回指向首个不小于给定键的元素的迭代器。
-  `upper_bound(x)` : 返回指向首个大于给定键的元素的迭代器。若容器内所有元素均小于或等于给定键,返回 `end()` 。
-  `empty()` : 返回容器是否为空。
-  `size()` : 返回容器内元素个数。

### 删除元素
## 遍历容器

如果你想删除 `Tom` 这个元素,则可以利用 `find` 函数找到 `Tom` ,然后再 `erase` 如下
可以利用迭代器来遍历关联式容器的所有元素。

```cpp
map<string, int>::iterator it;
it = mp.find("Tom");
mp.erase(it)
set<int> s;
typedef set<int>::iterator si;
for (si it = s.begin(); it != s.end(); it++) cout << *it << endl;
```

如果你想清空所有的元素,可以直接 `mp.clear()` 
需要注意的是,对 `map` 的迭代器解引用后,得到的是类型为 `pair<Key, T>` 的键值对。

### 其他函数
在 C++11 中,使用范围 for 循环会让代码简洁很多:

-  `count` 返回匹配特定键的元素出现的次数,例如 `mp.count("Tom")`
-  `swap` 可以交换两个 `map` ,例如 `swap(m1,m2)`
-  `size` 返回 `map` 中元素的个数。
-  `empty` 如果 `map` 为空则返回 `true` ,例如 `mp.empty()`
```cpp
set<int> s;
for (auto x : s) cout << x << endl;
```

##  `multimap` 
对于任意关联式容器,使用迭代器遍历容器的时间复杂度均为 $O(n)$ 。

`map` 类似, `multimap` 是有序键值对容器,但允许多个元素拥有同一键。其搜索、插入操作拥有对数复杂度。删除单个元素在最坏情况下具有对数复杂度,均摊为常数复杂度。
## 自定义比较方式

!!! warning
    正是因为 multimap 允许多个元素拥有同一键的特点,multimap 并没有提供给出键访问其对应值的方法。
 `set` 在默认情况下的比较函数为 `<` (如果是非内置类型需要 [重载 `<` 运算符](../op-overload.md#compare) )。然而在某些特殊情况下,我们希望能自定义 `set` 内部的比较方式。

### 插入与删除操作
这时候可以通过传入自定义比较器来解决问题。

- 通过向 `multimap` 中插入一个类型为 `pair<Key, T>` 的值可以达到插入元素的目的,例如 `mp.insert(pair<string,int>("Alan",100));`
-  `erase(x)` : 删除键为 x 的 **所有** 元素,返回删除元素的个数。
-  `erase(pos)` : 删除迭代器为 pos 的元素,要求迭代器必须合法。
-  `erase(first,last)` : 删除迭代器在 $[first,last)$ 范围内的所有元素。
-  `clear()` : 清空容器。
具体来说,我们需要定义一个类,并在这个类中 [重载 `()` 运算符](../op-overload.md#function)

### 查找操作
例如,我们想要维护一个存储整数,且较大值靠前的 `set` ,可以这样实现:

-  `count(x)` : 返回容器内键为 x 的元素数量。复杂度为 $O(\log(size)+ans)$ (关于容器大小对数复杂度,加上匹配个数)。
-  `find(x)` : 若容器内存在键为 x 的元素,会返回该元素的迭代器(如果有多个键为 x 的元素会返回任意一个);否则返回 `end()`
-  `lower_bound(x)` : 返回指向首个不小于给定键的元素的迭代器。
-  `upper_bound(x)` : 返回指向首个大于给定键的元素的迭代器。若容器内所有元素均小于或等于给定键,返回 `end()`
-  `empty()` : 返回容器是否为空。
-  `size()` : 返回容器内元素个数。
```cpp
struct cmp {
  bool operator()(int a, int b) { return a > b; }
};
set<int, cmp> s;
```

对于其他关联式容器,可以用类似的方式实现自定义比较,这里不再赘述。
+32 −11
Original line number Diff line number Diff line
@@ -160,40 +160,61 @@ $$
两边都加上 $B_{m + 1}$ ,即得到:

$$
\begin{aligned}
\sum_{j=0}^{m+1}\binom{m+1}{j}B_j&=[m=0]+B_{m+1}\\
\sum_{j=0}^{m}\binom{m}{j}B_j&=[m=1]+B_{m}\\
\sum_{j=0}^{m}\dfrac{B_j}{j!}\cdot\dfrac{1}{(m-j)!}&=[m=1]+\dfrac{B_{m}}{m!}
\end{aligned}$$
\end{aligned}
$$

设 $B(z) = \sum\limits_{i\ge 0}\dfrac{B_i}{i!}z^i$ ,注意到左边为卷积形式,故:
$$

B(z)e^z &= z+B(z)\\B(z) &=\\dfrac{z}{e^z - 1}\\end{aligned}$$
$$
\begin{aligned}
B(z)e^z &= z+B(z)\\
B(z)&=\dfrac{z}{e^z - 1}
\end{aligned}
$$

设 $F_n(z) = \sum_{m\ge 0}\dfrac{S_m(n)}{m!}z^m$ ,则:

$$
\begin{aligned}
F_n(z) &= \sum_{m\ge 0}\dfrac{S_m(n)}{m!}z^m\\
&= \sum_{m\ge 0}\sum_{i=0}^{n-1}\dfrac{i^mz^m}{m!}\\
\end{aligned}$$
\end{aligned}
$$

调换求和顺序:
$$

F*n(z) &=\\sum*{i=0}^{n-1}\\sum*{m\\ge 0}\\dfrac{i^mz^m}{m!}\\&=\\sum*{i=0}^{n-1}e^{iz}\\&=\\dfrac{e^{nz} - 1}{e^z - 1}\\&=\\dfrac{z}{e^z - 1}\\cdot\\dfrac{e^{nz} - 1}{z}\\end{aligned}$$
$$
\begin{aligned}
F_n(z) &=\sum_{i=0}^{n-1}\sum_{m\ge 0}\dfrac{i^mz^m}{m!}\\
       &=\sum_{i=0}^{n-1}e^{iz}\\
       &=\dfrac{e^{nz} - 1}{e^z - 1}\\
       &=\dfrac{z}{e^z - 1}\cdot\dfrac{e^{nz} - 1}{z}
\end{aligned}
$$

代入 $B(z)=\dfrac{z}{e^z - 1}$ :

$$
\begin{aligned}
F_n(z) &= B(z)\cdot\dfrac{e^{nz} - 1}{z}\\
&= \left(\sum_{i\ge 0}\dfrac{B_i}{i!} \right)\left(\sum_{i\ge 1}\dfrac{n^i z^{i - 1}}{i!}\right)\\
&= \left(\sum_{i\ge 0}\dfrac{B_i}{i!} \right)\left(\sum_{i\ge 0}\dfrac{n^{i+1} z^{i}}{(i+1)!}\right)
\end{aligned}$$
\end{aligned}
$$

由于 $F_n(z) = \sum_{m\ge 0}\dfrac{S_m(n)}{m!}z^m$ ,即 $S_m(n)=m![z^m]F_n(z)$ :
$$

S*m(n)&=m![z^m]F_n(z)\\&= m!\\sum*{i=0}^{m}\\dfrac{B*i}{i!}\\cdot\\dfrac{n^{m-i+1}}{(m-i+1)!}\\&=\\dfrac{1}{m+1}\\sum*{i=0}^{m}\\binom{m+1}{i}B_in^{m-i+1}\\end{aligned}$$
$$
\begin{aligned}
S \times m(n)&=m![z^m]F_n(z)\\
             &= m!\sum_{i=0}^{m}\dfrac{B \times i}{i!}\cdot\dfrac{n^{m-i+1}}{(m-i+1)!}\\
             &=\dfrac{1}{m+1}\sum_{i=0}^{m}\binom{m+1}{i}B_in^{m-i+1}
\end{aligned}
$$

故得证。