Skip to content

Commit

Permalink
10.23
Browse files Browse the repository at this point in the history
  • Loading branch information
dtcxzyw committed Oct 23, 2018
1 parent 5674cac commit e527e3b
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 17 deletions.
52 changes: 52 additions & 0 deletions Review/Backmatter.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
\chapter{后记}
\subsection{初稿}
2018年10月23日

写这本笔记大概花了我两个月的时间,此时这本笔记大概有200千字(50千字为中文),我
为自己的成果感到骄傲。在此期间我系统地复习了NOIP复赛以上的知识点,学到了许多新的方法/技巧/
思路,重新理解了之前看不懂的算法,收获颇多。我将自己的想法记录于此,以便之后重新翻阅。
该笔记在内容上还是较为完整的,在接下来的时间内我会在巩固的过程中继续学习并补充新的高级算法
/数据结构,以应战省选和NOI2019。

在我作为D类队员参加了NOI2018现场赛后,我发现了自己的严重问题——知识点掌握不扎实,
导致我没能拿到该得的分数。所以我决定自己写一写复习笔记来建立出一套自己的思维体系。

同时作为目前校内在OI道路上走的最远的人(其他人初赛都过不了。。。孤单),我没有
信息学强校学生所拥有的资源——学长。有学长的指导和遗留下的资料,能够少走许多弯路。
既然自己没有学长,只好自己做学长了。这本笔记算是是我的一份心意吧,希望学校的OI事业
能够有所发展,学弟们(???我好像根本不知道有学弟)如果需要我帮助的地方,随时联系我。
我的表达能力不太好,而且有些简单的地方没有细讲,你们可以打开页底下的参考资料链接细看
(这些资料是我自己能够接受并理解的),虽然这些东西在百度和维基上都能轻易找到,但我之前
根本就不知道这些东西的存在(直到考完后才知道,已经晚了)。。。所以你们就把它当做一种索引吧。

在写这本笔记的过程中,我发现自己在使用\LaTeX{}和排版方面还存在许多问题:
\begin{itemize}
\item 目录太长,分类过细
\item 参考资料链接过多
\item 不恰当的知识点分类
\item 过长的TODO List
\item 滥用lstlisting
\item 书籍排版密度过低
\item 滥用Index
\item 未大量使用BibTex
\item 数学公式排版掌握不熟
\item 缺少图片
\end{itemize}

在此我要感谢学校领导、老师和同学的理解与支持,当然也感谢父母(还有我妹)对我的
大力支持。\sout{差点忘了感谢CCF}。

刚开始写这本笔记时,我遇到了一件让我感到最幸福的事:我喜欢的女生向我表白了。
在此之前我已经预先认真考虑了几个月,所以当时我毫不犹豫就接受了。那个月是我最幸福的日子,
我开始改变自己,更加努力地学习,以应对将来需要承担的责任。她给了我写这本笔记的动力,她能够
理解许多别人所不能理解我的事情。我当时认为自己是这个世上最幸运的人。但好景不长,在本笔记
初稿即将完成时,我最担心的事情还是发生了——双方突然无话可说。我是预先知道这个问题会发生,
而且已经预先提醒过她了——遇到问题要多沟通,商量解决方案。但在短短几天内,她对我的态度越来
越差。最后她以一堆自相矛盾的理由离开了我,根本不给我与之沟通的机会。从前的誓言,以及我对
她的好,她似乎已经完全忘记了。但我仍然相信自己的单元测试是可靠的,她会回来的。我决定履行
自己之前的诺言:我等她两年(幸好当时没说一辈子,要不然我可能就凉了,忠于第二个人是很难的)。
我好想在她身边给她讲解数学题;好想在她生理期时给她端杯热水,陪在她身边;好想和她一起去公园
散步,在绿道慢跑,一起看动画电影;好想在她心情不好时,自己在旁边给她疏导,给她一个拥抱。唉,
这些愿望怕是无法实现了。现在自己还是努力学习,认真搞OI,考上理想的大学好了。得之我幸,不得
我命,未来会怎么样,得看自己的造化了。$\frac{\sin 4\theta}{\sin \theta}$,我等你回来。

5 changes: 3 additions & 2 deletions Review/Main.tex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
\usepackage[T1]{fontenc}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{ulem}
\lstset{
columns=fixed,
frame=none,
Expand Down Expand Up @@ -88,7 +89,7 @@ \chapter{前言}
\appendix
\include{Recommendation}
\printindex
\input{Bib.tex}
\input{Bib}
\backmatter
\chapter{后记}
\input{Backmatter}
\end{document}
6 changes: 3 additions & 3 deletions Review/NumberTheory/GCD.tex
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ \subsubsection{位扫描优化}
return LUT[(((v & -v) * 0x077CB531U)) >> 27];
}
\end{lstlisting}
这里的神奇常数{\bfseries 0x077CB531U}与De Bruijn Sequence
\index{D!De Bruijn Sequence}有关,在此不详细介绍。
这里的神奇常数{\bfseries 0x077CB531U}与De Bruijn Sequences
\index{D!De Bruijn Sequences}有关,在此不详细介绍。
LUT可预处理得出(但是不太好记住0x077CB531U)。
\index{*Constant!De Bruijn Sequence常数\\{\bfseries 0x077CB531U}}
\index{*Constant!De Bruijn Sequences常数\\{\bfseries 0x077CB531U}}
\end{itemize}

\subsubsection{实现}
Expand Down
38 changes: 32 additions & 6 deletions Review/Other/Mat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <cstdlib>
#include <cstring>
#include <random>
const int N = 1000, mod = 1000000007;
const int N = 2000, mod = 1000000007;
using Clock = std::chrono::high_resolution_clock;
class Timer {
private:
Expand Down Expand Up @@ -68,11 +68,36 @@ void mulOptimizedCache() {
*cij = *eij % mod;
}
}
void mulOptimizedUnfoldImpl() {}
const int bsiz = 32;
Int64 F[bsiz][bsiz], G[bsiz][bsiz];
void mulOptimizedBlockImpl() {}
void mulOptimizedBlock() {}
void mulOptimizedUnfold() {
const int step = 16;
memset(E, 0, sizeof(E));
for(int i = 0; i < N; ++i)
for(int k = 0; k < N; ++k) {
Int64 aik = A[i][k];
#define Unit(x) \
{ \
E[i][j + x] += aik * B[k][j + x]; \
if(E[i][j + x] >= end) \
E[i][j + x] %= mod; \
}

int j;
for(j = 0; j < N - step; j += step) {
Unit(0) Unit(1) Unit(2) Unit(3);
Unit(4) Unit(5) Unit(6) Unit(7);
Unit(8) Unit(9) Unit(10) Unit(11);
Unit(12) Unit(13) Unit(14) Unit(15);
}
while(j < N) {
Unit(0);
++j;
}
#undef Unit
}
for(int i = 0; i < N; ++i)
for(int j = 0; j < N; ++j)
C[i][j] = E[i][j] % mod;
}
#define test(func) \
{ \
memset(C, 0, sizeof(C)); \
Expand All @@ -98,6 +123,7 @@ int main() {
test(mulStandard);
test(mulOptimizedMod);
test(mulOptimizedCache);
test(mulOptimizedUnfold);
while(true)
getchar();
return 0;
Expand Down
111 changes: 110 additions & 1 deletion Review/Other/Owys.tex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
\section{卡常}
\subsection{取模}
\subsection{取模}\label{mod}
\begin{itemize}
\item 对于多个两整数乘积之和的取模(比如模意义下矩阵乘法),
可以设置一个阈值,(绝对值)超过该阈值才取模,最后再取一次取模。这种方法
Expand Down Expand Up @@ -36,8 +36,117 @@ \subsection{取模}
$\cdot =clamp(\cdot)$。在最后输出时$clamp$
\end{itemize}
\subsection{矩阵乘法}
不同优化下的矩阵乘法性能差异巨大,下面记录一些常用的优化。
\begin{itemize}
\item 见~\ref{mod}第一点;
\item 考虑访问矩阵时cache的连续性,
发现按照$i,j,k$访问时$B[k][j]$的访问位置跳跃较大,无法cache,性能较低;
但是如果按照$i,k,j$的顺序计算,就可以使$C[i][j]$$B[k][j]$的访问位置连续,
提高访问速度;
\item 在第3层循环内进行循环展开。
\end{itemize}
性能测试代码:
\lstinputlisting{Other/Mat.cpp}

2000*2000矩阵乘法性能测试结果如下(i7-4790K):

mulStandard 105793.233 ms AC

mulOptimizedMod 27042.733 ms AC

mulOptimizedCache 12499.686 ms AC

mulOptimizedUnfold 11096.677 ms AC

每个算法由上一个算法修改而来,mulOptimizedUnfold使用了所有优化,
比原始算法快了接近10倍,可见对矩阵乘法进行优化还是很划算的。
\subsection{基于硬件的优化}
\begin{itemize}
\item 循环展开:指定一个步长,满步长区间硬编码,;
\item 多路并行:在循环展开的同时避免修改同一变量,即保持循环间的独立,
这样有助于CPU流水线的并行;
\item Cache优化:尽可能保证循坏时访问位置连续;
\item 尽可能使用临时变量,这样可以保持数据在寄存器中,
最后再写回数组。
\end{itemize}
\subsection{位运算}
\subsubsection{符号判断}
\begin{displaymath}
sign(x)=\left\{\begin{array}{cc}
1&x>0\\
0&x=0\\
-1&x<0
\end{array}\right.
\end{displaymath}
\begin{lstlisting}
int sign(int x) {
return (x>0)-(x<0);
}
\end{lstlisting}
此法同样适用于浮点数符号的判断。
\subsubsection{判断异号}
\begin{lstlisting}
bool flag=((x^y)<0);
\end{lstlisting}
\subsubsection{绝对值}
\begin{lstlisting}
int iabs(int x) {
int mask=x>>31;
return (x+mask)^mask;
}
\end{lstlisting}
$x$为非负则为$(x+0)\oplus x=x$,若$x$为负则为$(x-1)\oplus 0xffffffff=-x$
注意有符号右移时高位补符号位)。
\subsubsection{去末尾1}
\index{B!Brian Kernigan's Bit Counting}
$v\&=v-1$,即使$v$末尾的$100\cdots$部分与$011\cdots$按位与。
\subsubsection{取末尾0的数量}
首先使用$w=v\&-v$取得最低位1,然后无符号乘以De Bruijn Sequences
\index{D!De Bruijn Sequences}常数0x077CB531U,最后前五位与每种$w$
一一对应。LUT可预处理。
\begin{lstlisting}
int countTZ(int x) {
unsigned int bit=x&-x;
return LUT[(bit*0x077CB531U)>>27];
}
\end{lstlisting}
\subsubsection{子集枚举}
一般使用如下写法:
\begin{lstlisting}
for(int i=S;i;i=(i-1)&S)
\end{lstlisting}
可以理解为每次都做一次忽略S非零位的减法,从全集开始枚举自然能够
枚举所有子集。

以上算法参考了Sean Eron Anderson的文章\footnote{
Bit Twiddling Hacks
\url{http://graphics.stanford.edu/\~seander/bithacks.html}
}。
\subsection{搜索优化}
\begin{itemize}
\item 维护全局最优值,尽可能剪枝;
\item 对于一些计算几何题,通过随机旋转坐标系+不正确的贪心来提高
寻找最优解的速度(也可以作为预处理指导剪枝)。
\item 在求权值最优的点对时,使用随机算法骗分;
\item 对于多次修改+全局询问的问题,其最优解不会移动太远,
考虑从上一个最优解开始移动搜索。例如[ZJOI2015]幻想乡战略游戏。
\end{itemize}
\subsection{数组清零}
\begin{itemize}
\item 整个数组的清零可以使用memset,因为它的实现可能有循环展开/SIMD优化。
\item 若仅修改整个数组的部分数据,可以重新扫一遍修改时的数据,撤销修改操作/
直接将对应位置置0(这会影响到算法时间复杂度,尤其是对于Dsu On Tree/cdq分治);
\item 对于树状数组,在模拟树状数组修改算法置零时,若当前值为0,则直接退出,因为
接下来的值肯定都为0(肯定在清除其他链上数时被清零了)。
\begin{lstlisting}
void clear(int x) {
while(x<=siz && A[x]) {
A[x]=0;
x+=x&-x;
}
}
\end{lstlisting}
\item 对于bool数组无需清零,使用int记录其最后一次被标记为true的时间戳
$timeStamp$,若时间戳与当前时间戳相等则为true,将当前时间戳+1即可$O(1)$清零。
对于其它值的存储也可以如此清零(std::pair<时间戳,实际数据>)。
\end{itemize}
10 changes: 5 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,11 @@
- [x] 二进制分组
## 卡常
- [x] 取模
- [ ] 矩阵乘法
- [ ] 基于硬件的优化
- [ ] 搜索优化
- [ ] 位运算技巧 子集枚举
- [ ] 数组的清零(对修改部分清零,时间戳)
- [x] 矩阵乘法
- [x] 基于硬件的优化
- [x] 搜索优化
- [x] 位运算技巧 子集枚举
- [x] 数组的清零(对修改部分清零,时间戳)
## 理论
- [x] 主定理
- [x] NP完全性
Expand Down

0 comments on commit e527e3b

Please sign in to comment.