前言

一年一度的暑假又到了。oiclass 第四届夏令营也来了。相信不少人 AC 题目之后都有一种喜悦感,想通过发题解分享自己的思路和代码。而不规范的题解不但不美观,还可能误导新手,因此,这里写一些题解的建议。

本人文采不好及水平有限,请见谅。本文较长,还请大家耐心阅读。

一、题解的内容

一篇题解不应该只有代码。如果你只给出代码,这对于新手来说很难读懂。

一篇好的题解可以包含以下内容:

  1. 题目大意(可有可无)
  2. 你的思路(必须有)
  3. 代码(可有可无)
  4. 图片 / 外部链接(可有可无)
  5. 其他(包括但不限于用到算法的讲解)

此外,题解不应该包括太多的无意义内容,比如求赞、小游戏、闲聊、吐糟。

对于模板题,还要对这题用到的算法进行解释,以便读者理解。

二、正确使用 Markdown

正确的使用 Markdown,可以让文章主次分明,展示美观。而错误的 Markdown,会让人阅读困难。

如果你不清楚什么是 Markdown,可以自行百度

实际上,每当你发帖 / 发博客的时候,你都会发现右边有一栏字(如图):

这里是一些基础 MarkDown 用法。你在发题解的时候可以看着来写。

此外,还有一些 Markdown 语法,在 oiclass 都已经有了快捷按钮,你也可以自己手打。这里介绍几种图中没有提及的 Markdown:

  • 标题

标题在 oiclass 的快捷键是 Ctrl + Alt + 161\sim6 中的一个数字。标题分为一级标题到六级标题。其中标题的级别越大,字体也越大。面对有很多部分的内容时,可以使用标题,但不要滥用标题。我自己的偏好是三级标题,包括这篇文章用的也是三级标题。 标题源码:

# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题

效果:

一级标题

二级标题

三级标题

四级标题

五级标题
六级标题

注意:无论是几级标题,"#" 后面都应该加空格。

  • 粗体、斜体与删除线

粗体、斜体、删除线在 oiclass 的快捷键分别是 Ctrl + B、Ctrl + I(是大写字母 I)与 Ctrl + D。他们的用处应该我不用多说了吧。

源码:

这是**粗体**、*斜体*和~~删除线~~。

效果:

这是粗体斜体删除线

需要注意的是,标点符号一般不会放在粗体、斜体、删除线里。否则就会像下面一样:

源码:

**这是加粗字体。**这是不加粗字体。

效果:

**这是加粗字体。**这是不加粗字体。

你会发现,“这是加粗字体”并没有被真正的加粗。而解决方案吗,就是在后面的“**”后面加上一个空格。

改进后的源码:

**这是加粗字体。** 这是不加粗字体。

效果:

这是加粗字体。 这是不加粗字体。

虽然成功了,但是不太美观。因此不建议把标点符号放在粗体、斜体、与删除线里。

关于粗体的一个注意事项:一篇题解不要用太多的粗体,这样会显得你的文章没有重点。

  • 有序列表与无序列表

有序列表和无序列表都可以来表达并列的信息。他们在 oiclass 的快捷键分别是 Ctrl + L 和 Ctrl + O。

源码:

+ 无序列表写法一
+ 无序列表写法一
- 无序列表写法二
- 无序列表写法二
* 无序列表写法三
* 无序列表写法三
1. 有序列表第一项
2. 有序列表第二项
3. 有序列表第三项

效果:

  • 无序列表写法一
  • 无序列表写法一
  • 无序列表写法二
  • 无序列表写法二
  • 无序列表写法三
  • 无序列表写法三
  1. 有序列表第一项
  2. 有序列表第二项
  3. 有序列表第三项

和标题的注意事项一样,列表的"+/-/*/数字"后面都要加空格。

  • 表格

表格在 oiclass 的快捷键是 Ctrl + M。

源码:

居左的表格:

| 表头 1 | 表头 2 |
| :--- | :--- |
| 1 | 2 |

居中的表格:

| 表头 1 | 表头 2 |
| :---: | :---: |
| 1 | 2 |

居右的表格:

| 表头 1 | 表头 2 |
| ---: | ---: |
| 1 | 2 |

效果:

居左的表格:

表头 1 表头 2
1 2

居中的表格:

表头 1 表头 2
1 2

居右的表格:

表头 1 表头 2
1 2

注:表格第二行中的"-"号数量不需要一定是 3 个,整行数量一致即可。

  • 空格的使用

大家会发现,我在这篇文章的一些地方用了空格。

那什么时候要用空格呢?

中文与西文字符或公式之间以一个半角空格隔开,​但标点符号与西文字符或公式间不要加空格​。

相信肯定会有人不理解,那就举个例子吧。

错误的:

这道题用时间复杂度是$O(n^2)$的代码无法通过,要用二分优化。

这道题用时间复杂度是O(n2)O(n^2)的代码无法通过,要用二分优化。

正确的:

这道题用时间复杂度是 $O(n^2)$ 的代码无法通过,要用二分优化。

这道题用时间复杂度是 O(n2)O(n^2) 的代码无法通过,要用二分优化。

用了空格不会让公式显得过于拥挤,但是 oiclass 不强求,可用可不用。

三、KaTeX\KaTeX 的使用

在上面的例子中,出现了 $O(n^2)$ 这样的语句。其实,他就是 KaTeX\KaTeX

KaTeX\KaTeX 是一个支持 HTML 的轻量级的数学公式引擎,它由 Khan Academy 开发,使用起来也非常简单。

  1. KaTeX\KaTeX 的使用场景

并不是一切东西都该放在公式中的。滥用公式会导致渲染速度下降,排版混乱等后果。

所以什么东西不该放在公式中呢?

  1. 中文一般不要放在 LaTeX\LaTeX 中。
  2. 算法名,人名等非公式内容一般不要放在 LaTeX\LaTeX 中。
  3. 行内的程序代码(包括程序函数名称,变量类型,完整语句等)应该用行内代码框表示,而不放在公式中。

.

  1. 常见的 KaTeX\KaTeX 语法。

注:以下内容只是一些常用的 KaTeX\KaTeX 语法。

  • 运算符
$+ - \mp \pm \times \div\sqrt{n}\sqrt{3}{n}$

$+\quad -\quad \mp\quad \pm\quad \times\quad \div\quad \sqrt{n}\quad\sqrt[3]{n}$

  • 集合
$\{ \} \in \not\in \varnothing \cap \cup$

$\{\quad \}\quad \in\quad \not\in\quad \varnothing\quad \cap\quad \cup$

  • 关系符号
$= \ne \approx \le \ge < > \sim$

$=\quad \ne\quad \approx\quad \le\quad \ge\quad <\quad >\quad\sim$

  • 几何符号
$\perp 45^\circ$

45\perp\quad 45^\circ

  • 逻辑符号
$\forall \exists \nexists \therefore \because \And \lor \land \lnot$

$\forall\quad \exists\quad \nexists\quad \therefore\quad \because\quad\And\quad \lor\quad \land\quad \lnot$

  • 上标与下标
$a^2 a_{1+1} a^2_1 {}^1_2 a^1_2$

a2a1+1a1221a21a^2\quad a_{1+1}\quad a^2_1\quad {}^1_2 a^1_2

  • 特殊符号
$\bigcirc \Box \blacksquare$

\bigcirc\quad \Box\quad \blacksquare

  • 求和、求积
$\sum\limits_{i=1}^n a_i \prod\limits_{i=1}^n a_i$

$\sum\limits_{i=1}^n a_i\quad \prod\limits_{i=1}^n a_i$

  • 取模
$\% \bmod a\equiv1\pmod p\mid \nmid$

$\%\quad \bmod\quad a\equiv1\pmod p\quad\mid\quad\nmid$

  • 分数
$\frac{1}{2} \dfrac{1}{3} \dfrac{1}{x+\dfrac{1}{y+\dfrac{1}{z}}}$

$\frac{1}{2}\quad \dfrac{1}{3}\quad \dfrac{1}{x+\dfrac{1}{y+\dfrac{1}{z}}}$

  • 矩阵
$\begin{bmatrix}a&b\\c&d\end{bmatrix}$

$\begin{Bmatrix}a&b\\c&d\end{Bmatrix}$

$\begin{vmatrix}a&b\\c&d\end{vmatrix}$

[abcd]\begin{bmatrix}a&b\\c&d\end{bmatrix}

{abcd}\begin{Bmatrix}a&b\\c&d\end{Bmatrix}

abcd\begin{vmatrix}a&b\\c&d\end{vmatrix}

  • 条件定义(如分段函数)或方程组
$f(x)=\begin{cases}x+1&x>1\\x^2-1&x\le1\end{cases}$

$\begin{cases}x^2-3x+1=3\\x^3-5x^2+x-10=1\end{cases}$

f(x)={x+1x>1x21x1f(x)=\begin{cases}x+1&x>1\\x^2-1&x\le1\end{cases}

$\begin{cases}x^2-3x+1=3\\x^3-5x^2+x-10=1\end{cases}$

  • 多行等式
$\begin{aligned}C_n^2&=\dfrac{A_n^2}{A_2^2}\\&=\dfrac{n\times(n-1)}{2}\end{aligned}$

$\begin{aligned}C_n^2&=\dfrac{A_n^2}{A_2^2}\\&=\dfrac{n\times(n-1)}{2}\end{aligned}$

  • 空格
$a\,b a\ b a\quad b a\qquad b$

$a\,b\quad\quad a\ b \quad\quad a\quad b \quad\quad a\qquad b$

  1. KaTeX\KaTeX 使用误区

.

  1. 要用 Roman 字体来写函数。

错误的:

$lcm(a,b)\times gcd(a,b)=a\times b$

lcm(a,b)×gcd(a,b)=a×blcm(a,b)\times gcd(a,b)=a\times b

正确的:

$\operatorname{lcm}(a,b)\times\gcd(a,b)=a\times b$

lcm(a,b)×gcd(a,b)=a×b\operatorname{lcm}(a,b)\times\gcd(a,b)=a\times b

简单来说,就是像 log,gcd\log,\gcd 这样已经定义好的函数就在前面加 \,否则就用 \operatorname{} 括起来函数名称。

  1. 对于字符串常量,要使用打印机字体。

错误的:

$s=IAKIOI$

s=IAKIOIs=IAKIOI

正确的:

$s=\texttt{IAKIOI}$

s=IAKIOIs=\texttt{IAKIOI}

  1. 对于非公式内容,要使用 \text{}

错误的:

$f(x)=\begin{cases}1&x\in prime\\0&otherise\end{cases}$

$f(x)=\begin{cases}1&x\in prime\\0&otherise\end{cases}$

正确的:

$f(x)=\begin{cases}1&x\in \text{prime}\\0&\text{otherise}\end{cases}$

$f(x)=\begin{cases}1&x\in \text{prime}\\0&\text{otherise}\end{cases}$

  1. 公式不是写代码的地方

我们写的是非常正规的数学公式,因此​不要在非代码区域使用任何程序设计语言的表示方式​。

错误的:

x++
a=x\%p

x++a=x%p\begin{matrix}x++\\a=x\%p\end{matrix}

正确的:

x\gets x+1
a=x\bmod p

xx+1a=xmodp\begin{matrix}x\gets x+1\\a=x\bmod p\end{matrix}

  1. 数组的表示

错误的:

$a[i][j]$

a[i][j]a[i][j]

这里错误的原因和上一条一样。

正确的:

$a_{i,j}$ 或 $a(i,j)$

ai,ja_{i,j}a(i,j)a(i,j)

四、代码的要求

人与人之间的代码风格差异还是挺大。不过题解对代码风格的要求并不高,只需要可读性好即可。

不要写恶搞题解。如明显超出本题范畴的题解。反例就是 A+B Problem 的题解区里的部分题解。

作为一篇题解,代码肯定不能乱写。此外,代码应该要有恰当的注释,避免喧宾夺主。

下面是一个典型的注释喧宾夺主的例子(/* */ 注释的部分指出问题所在):

#include<bits/stdc++.h>//万能头文件
/* 这个头文件是万能头文件很重要吗? */
using namespace std;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);//优化cin,cout,险些TLE。
    /* 同前。 */
    int a[100005],n;
    cin>>n; // 输入礼物数。
    /* 题目输入格式里已经把输入的每个数字的含义写的很清楚了。 */
    for(int i=1;i<=n;i++)//代表n个礼物。
    /* 还能是几个礼物? */
    {
        cin>>a[i];//输入坐标。
        /* 同前。 */
        a[i]=min(a[i]-1,1000000-a[i]);//取最小值min。
        /* 是当读者完全不知道 min 函数吗? */
    }
    sort(a+1,a+1+n);//排序后取最大值。
    /* 同前。 */
    cout<<a[n];//将最大值输出。
    /* 同前。 */
    return 0;//结束。
    /* 不是结束还能是啥? */
}
/* 总结:请不要把读者当完全没学过语言的傻子。 */

以下是一个可读性不大的例子:(洛谷 P2357)

#include<bits/stdc++.h>
#define endl '\n'
typedef long long ___________;
using namespace std;
___________ _____,_________,_[200001],_1,_2,_3,__[200001],___[200001],________;
___________ _______(___________ _){return _ & (-_);}
void ____(___________ _,___________ ______){for(___________ __________ = _;__________ <= _____;__________ += _______(__________))__[__________] += ______,___[__________] += (_ - 1) * ______;}
___________ ______(___________ x){___________ ______ = 0;for(___________ __________ = x;__________ >= 1;__________ -= _______(__________))______ += x * __[__________] - ___[__________];return ______;}
int main(){
	cin >> _____ >> _________;
	for(int __________ = 1;__________ <= _____;__________++)cin >> _[__________],____(__________,_[__________] - _[__________ - 1]);
	while(_________--){
		cin >> ________;
		if(________ == 1)cin >> _1 >> _2 >> _3,____(_1,_3),____(_2 + 1,-_3);
		else if(________ == 2)cin >> _3,____(1,_3),____(2,-_3);
		else if(________ == 3)cin >> _3,____(1,-_3),____(2,_3);
		else if(________ == 4)cin >> _1 >> _2,cout << ______(_2) - ______(_1 - 1) << endl;
		else if(________ == 5)cout << ______(1) << endl;
	}
}

此外,这个代码也没有注释。

后记

写这个博客写了大半天了。我写这个的起因是看到了这个讨论,我想:我也得给 oiclass 做点贡献。

写这个博客的时候参考了很多文章,也发现了自己的许多不足。

希望大家写题解的时候能参考这篇文章,不要写格式、内容不对的题解。

我最近看到了有人抄题解,我希望大家不要养成这种坏习惯,自己想思路自己写代码。

最后说一句:写题解未必是一件简单的事情。如果你想写一篇优质的题解,就必须为此付出更多的时间与精力。在写题解之前,你必须准备好耗费一个小时甚至几个小时的情况。

那这篇博客就到此结束了。

附录:参考资料

  1. 题解审核及反馈要求 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
  2. 如何用 Markdown&LaTeX 写一篇排版整齐的题解? - Studying Father's luogu blog - 洛谷博客
  3. LaTeX 数学公式大全 - Iowa_BattleShip 的博客 - 洛谷博客 (luogu.com.cn)