Unverified Commit 64dc7f19 authored by Shuhao Zhang's avatar Shuhao Zhang Committed by GitHub
Browse files

Merge pull request #7 from 24OI/master

sync 2019.3.19
parents dbe912cc 4e902217
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -2,8 +2,11 @@

- 请在 commit 的时候写比较有意义的 commit message
- 请给 PR 起比较有意义的标题。       
- 关于同一类尚未合并的 PR 修改请直接在对应的 fork / 分支中进行,除非已有的内容已被合并需要修改,否则**不要多开 PR**
- 请在 PR 之前检查一下您的 PR 是否存在以下常见问题:(确认无问题后请将选项打钩 / 填为 `[x]`
   * [ ] 您的 MD 代码的书写格式,包括但不限于 **中文与英文之间、中文与阿拉伯数字、中文与 LaTeX 公式之间要有一个半角空格**,特别地,在中文全角符号与英文、阿拉伯数字、LaTeX 公式之间,**不需要**半角空格。(这个可以使用自动化工具辅助,比如 https://github.com/baurine/vscode-pangu)  
   * [ ] 请务必确保您的文档中引用的**外链**图片已经全部转存到了**本库内**对应的`images`文件夹中(防止触发某些网站的防盗链),并已全部处理成了`MD文档名称+编号`的形式(可参考已有文档中图片的处理方式)  
   * [ ] 请确保您的文档中的引用链接的稳定性,**不推荐**引用**自建**服务(如OJ)中的资源(如题目)
   * [ ] 对于 LaTeX 公式,请注意常见的问题,**一定要使用** `$\log$``$\min$``$\max$``$\gcd$` 等,而非 `$log$``$min$``$max$``$gcd$`。对于最小公倍数,请使用 `$\operatorname{lcm}$` 而非 `$lcm$`,省略号请使用 `$\cdots$`,叉乘请使用 `$\times$`,点乘请使用 `$\cdot$`
   * [ ] 所有公式中的希腊字母等特殊符号,请不要使用输入法的插入特殊符号功能,而应该使用对应的 LaTeX 公式符号。如 phi 大多数情况下应该使用 `$\varphi$` 而不是 `$\phi$`
   * [ ] 行间公式前后各要有一行空行。
+1 −1
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
# 欢迎来到 **OI Wiki**!

[![Travis](https://img.shields.io/travis/24OI/OI-wiki.svg?style=flat-square)](https://travis-ci.org/24OI/OI-wiki)
[![Progress](https://img.shields.io/badge/Progress-78%25-brightgreen.svg?style=flat-square)](https://github.com/24OI/OI-wiki)
[![Progress](https://img.shields.io/badge/Progress-84%25-brightgreen.svg?style=flat-square)](https://github.com/24OI/OI-wiki)
[![Uptime Robot Status](https://img.shields.io/uptimerobot/status/m781254113-3e3bac467c64fc99eafd383e.svg?style=flat-square)](https://status.oi-wiki.org/)
[![Telegram](https://img.shields.io/badge/OI--wiki-join%20Telegram%20chat-brightgreen.svg?style=flat-square)](https://t.me/OIwiki)
[![QQ](https://img.shields.io/badge/OI--wiki-join%20QQ%20group-brightgreen.svg?style=flat-square)](https://jq.qq.com/?_wv=1027&k=5EfkM6K)
+64 −4
Original line number Diff line number Diff line
@@ -11,6 +11,12 @@ C/C++ 将文件分为文本文件和二进制文件。文本文件就是简单

##  `freopen` 函数

### 函数简介

函数用于将指定输入输出流以指定方式重定向到文件,包含于头文件 `stdio.h (cstdio)` 中,该函数可以在不改变代码原貌的情况下改变输入输出环境,但使用时应当保证流是可靠的

函数主要有三种方式:读、写和附加

### 命令格式

```cpp
@@ -20,8 +26,25 @@ FILE* freopen(const char* filename, const char* mode, FILE* stream);
### 参数说明

-    `filename` : 要打开的文件名
-    `mode` : 文件打开的模式
-    `stream` : 文件指针,通常使用标准文件流 ( `stdin/stdout/stderr` )
-    `mode` : 文件打开的模式,表示文件访问的权限
-    `stream` : 文件指针,通常使用标准文件流 ( `stdin/stdout` ) 或标准错误输出流 (`stderr`) 
-    返回值:文件指针,指向被打开文件

### 文件打开格式(选读)

- `r`:以只读方式打开文件,文件必须存在,只允许读入数据**(常用)**
- `r+`:以读 / 写方式打开文件,文件必须存在,允许读写数据
- `rb`:以只读方式打开二进制文件,文件必须存在,只允许读入数据
- `rb+`:以读写方式打开二进制文件,文件必须存在,允许读写数据
- `rt+`:以读写方式打开文本文件,允许读写
- `w`:以只写方式打开文件,文件不存在会新建文件,否则清空内容,只允许写入数据**(常用)**
- `w+`:以读 / 写方式打开文件,文件不存在将新建文件,否则清空内容,允许读写数据
- `wb`:以只读方式打开二进制文件,文件不存在将会新建文件,否则清空内容,只允许写入数据
- `wb+`:以读写方式打开二进制文件,文件不存在将新建文件,否则清空内容,允许读写数据
- `a`:以附加方式打开只写文件,文件不存在将新建文件,写入数据将被附加在文件末尾(保留EOF符)
- `a+`:以附加方式打开只写文件,文件不存在将新建文件,写入数据将被附加在文件末尾(不保留EOF符)
- `at+`:以读写方式打开文本文件,写入数据将被附加在文件末尾
- `ab+`:以读写方式打开二进制文件,写入数据将被附加在文件末尾

### 使用方法

@@ -46,6 +69,9 @@ fclose(stdin);
fclose(stdout);
```

!!! 注
    `printf/scanf/cin/cout`等函数默认使用 `stdin/stdout`,将 `stdin/stdout`重定向后,这些函数将输入 / 输出到被定向的文件

### 模板

```cpp
@@ -65,6 +91,40 @@ int main(void) {

参考书目:信息学奥赛一本通

## `fopen`函数(选读)

函数大致与 `freopen` 相同,函数将打开指定文件并返回打开文件的指针

### 函数原型

```cpp
FILE * fopen(const char * path, const char * mode)
```

各项参数含义同 `freopen` 

### 可用读写函数(基本)

- `fread/fwrite`
- `fgetc/fputc`
- `fscanf/fprintf`
- `fgets/fputs`

### 使用方式

```cpp
FILE *in, *out; // 定义文件指针
in = fopen("data.in", "r");
out = fopen("data.out", "w");
/*
do what you want to do
*/
fclose(stdin);
fclose(stdout);
```



## C++ 的 `ifstream/ofstream` 文件输入输出流

### 使用方法
@@ -73,14 +133,14 @@ int main(void) {

```cpp
ifstream fin("data.in");
// data.in 就是读取文件名,要和可执行文件放在同一目录下
// data.in 就是读取文件的相对位置或绝对位置
```

输出到文件:

```cpp
ofstream fout("data.out");
// data.out 就是输出文件的文件名,和可执行文件在同一目录下
// data.out 就是输出文件的相对位置或绝对位置
```

关闭标准输入/输出流

docs/dp/backpack.md

deleted100644 → 0
+0 −71
Original line number Diff line number Diff line
在学习本章前请确认你已经学习了[动态规划部分简介](/dp/)

在具体讲何为 "背包 dp" 前,先来看如下的例题

??? note " 例题[\[USACO07DEC\]手链 Charm Bracelet](https://www.luogu.org/problemnew/show/P2871)"
    本题题意可概括为——N 物体,放入容量为 M 的背包,要求使总价值最大。由于每个物体只有 2 种情况——取与不取,正如二进制中的 0 和 1——这类问题便被称为“0-1 背包问题”。

## 0-1 背包

例题中已知条件有第 i 个物体的体积 v[i]和价值 w[i], 背包总容量

显而易见的是,可以计算总价值的,只有已经放入背包的物体,因此该题中对 "是否为最大值" 的判断是建立在 "已经放入背包之中" 的基础之上的

已知对于一个容量为 v1,可以放置第 1 到第 i 件物体的背包,其最大总价值很明显等于容量为 v1 的背包,放有第 1 到第 (i-1) 件物体时的最大值(第 i 件物体不取时)或者是容量为 v1-v[i]的背包,放有第 1 到第 (i-1) 件物体时的最大值 + w[i](第i件物体取时)

由此可以得出状态转移方程

-   dp[v1][i]=max(dp[v1][i-1],dp[v1-v\[i\]][i-1]+w[i])

有了这样的思路,就可以顺利地写出代码了

```cpp
for (int i = 1; i <= v1; i++)
  for (int l = 0; l <= v1 - i; l++) dp[l + i] = max(dp[l] + w[i], dp[l + i]);
```

按照正确的思路,写出了这样的核心代码,然后就可以提交……

错!

让我们再回头看一下代码,i 表示当前判断的是第 i 个物体,l 则穷举体积,可是注意一个地方——l 是从 0 到 v1-v[i]

这意味着什么呢?举个栗子,可能在体积为 (l) 处取物体 i 新的 dp 值存到体积为 (l+v[i]) 处,而在体积为 (l+v[i]) 处,物体 i 再次被取

所以,当以 0~v1-v[i]的顺序穷举时,物体实际上可能被加入多遍,这显然与题意不符

因此为了避免多取,穷举顺序应为 v1-v[i]~0

因此实际核心代码为

```cpp
for (int i = 1; i <= v1; i++)
  for (int l = v1 - i; l >= 0; l--) dp[l + i] = max(dp[l] + w[i], dp[l + i]);
```

例题代码

```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 13010;
int n, v, c[maxn], w[maxn], most[maxn];
int main() {
  cin >> n >> v;
  for (int i = 1; i <= n; i++) {
    cin >> c[i] >> w[i];
  }
  for (int i = 1; i <= n; i++)
    for (int l = v; l >= c[i]; l--) {
      if (most[l - c[i]] + w[i] > most[l]) most[l] = most[l - c[i]] + w[i];
    }
  cout << most[v];
  return 0;
}
```

Ps. 事实上,由小到大穷举是另一种背包问题的解法,稍后会提到

## 完全背包

## 多重背包

docs/dp/knapsack.md

0 → 100644
+69 −0
Original line number Diff line number Diff line
在学习本章前请确认你已经学习了[动态规划部分简介](/dp/)

在具体讲何为 "背包 dp" 前,先来看如下的例题

??? note " [\[USACO07 DEC\]Charm Bracelet](https://www.luogu.org/problemnew/show/P2871)"
    题意概要:有 $n$ 个物品和一个容量为 $W$ 的背包,每个物品有重量 $w_{i}$ 和价值 $v_{i}$ 两种属性,要求选若干物品放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容量。
    
在上述例题中,由于每个物体只有 $2$ 种可能的状态(取与不取),正如二进制中的 $0$ 和 $1$,这类问题便被称为 “0-1 背包问题”。

## 0-1 背包

例题中已知条件有第 $i$ 个物品的重量 $w_{i}$,价值 $v_{i}$,以及背包的总容量 $W$。

设 DP 状态 $f_{i,W}$ 为在只能放前 $i$ 个物品的情况下,容量为 $W$ 的背包所能达到的最大总价值。

考虑转移。假设当前已经处理好了前 $i-1$ 个物品的所有状态,那么对于第 $i$ 个物品,当其不放入背包时,背包的剩余容量不变,背包中物品的总价值也不变,故这种情况的最大价值为 $f_{i-1,W}$;当其放入背包时,背包的剩余容量会减小 $w_{i}$,背包中物品的总价值会增大 $v_{i}$,故这种情况的最大价值为 $f_{i-1,W-w_{i}}+v_{i}$。

由此可以得出状态转移方程:

$$f_{i,W}=\max(f_{i-1,W},f_{i-1,W-w_{i}}+v_{i})$$

在程序实现的时候,由于对当前状态有影响的只有 $f_{i-1}$,故可以去掉第一维,直接用 $dp_{W}$ 来表示处理到当前物品 $i$ 时背包容量为 $W$ 的最大价值 $f_{i,W}$。

还有一点需要注意的是,很容易写出这样的错误核心代码:

```cpp
for (int i = 1; i <= W; i++)
  for (int l = 0; l <= W - i; l++)
    dp[l + w[i]] = max(dp[l] + v[i], dp[l + w[i]]);
    // 由 f[i][l + w[i]] = max(max(f[i - 1][l + w[i]],f[i - 1][l] + w[i]),f[i][l + w[i]]); 简化而来
```

这段代码哪里错了呢?枚举顺序错了。

仔细观察代码可以发现:对于当前处理的物品 $i$ 和当前状态 $f_{i,W}$,在 $W\geqslant w_{i}$ 时,$f_{i,W}$ 是会被 $f_{i,W-w_{i}}$ 所影响的。这就相当于物品 $i$ 可以多次被放入背包,与题意不符。(事实上,这正是完全背包问题的解法)

为了避免这种情况发生,我们可以改变枚举的顺序,从 $W$ 枚举到 $w_{i}$,这样就不会出现上述的错误,因为 $f_{i,W}$ 总是在 $f_{i,W-w_{i}}$ 前被更新。

因此实际核心代码为

```cpp
for (int i = 1; i <= W; i++)
  for (int l = W - i; l >= 0; l--)
    dp[l + i] = max(dp[l] + w[i], dp[l + i]);
    // 由 f[i][l + w[i]] = max(max(f[i - 1][l + w[i]],f[i - 1][l] + w[i]),f[i][l + w[i]]); 简化而来
```

??? 例题代码

    ```cpp
    #include <iostream>
    const int maxn = 13010;
    int n, v, w[maxn], v[maxn], dp[maxn];
    int main() {
      std::cin >> n >> W;
      for (int i = 1; i <= n; i++)
        std::cin >> w[i] >> v[i];
      for (int i = 1; i <= n; i++)
        for (int l = W; l >= w[i]; l--)
          if (dp[l - w[i]] + v[i] > dp[l])
            dp[l] = dp[l - w[i]] + v[i];
      std::cout << dp[W];
      return 0;
    }
    ```

## 完全背包

## 多重背包
Loading