Skip to content

Commit

Permalink
2.5
Browse files Browse the repository at this point in the history
  • Loading branch information
dtcxzyw committed Feb 5, 2019
1 parent 25efec9 commit 5964d85
Show file tree
Hide file tree
Showing 36 changed files with 1,233 additions and 55 deletions.
5 changes: 4 additions & 1 deletion Queue.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,12 @@
- [ ] Codeforces 596E Wilbur and Strings
- [ ] P3649
- [ ] P3757
- [ ] LOJ#6198. 谢特(SAMParent数+Trie合并)
- [ ] LOJ#6198. 谢特(SAMParent树+Trie合并)
- [ ] BZOJ3277: 串
- [x] BZOJ2780: [Spoj]8093 Sevenk Love Oimaster
# AC自动机
- [x] LOJ#2180. 「BJOI2017」魔法咒语
- [ ] BZOJ2780: [Spoj]8093 Sevenk Love Oimaster
# KMP
- [ ] LOJ#2272. 「SDOI2017」文本校正
# 线性基
Expand Down
2 changes: 1 addition & 1 deletion Review/NumberTheory/PrimalityTest.tex
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ \subsubsection{强Lucas测试}
$U_{n-\Jacobi{D}{n}}\not \equiv 0\pmod{n}$,则$n$必为合数。
对于$\Jacobi{D}{n}=-1$,该条件改为$U_{n+1}\not \equiv 0\pmod{n}$

此外可以检查$V_{n+1}\not \equiv 2Q \pmod{n}$满足任意一个条件即可判定它为合数
此外可以检查$V_{n+1}\not \equiv 2Q \pmod{n}$满足任意一个条件就判定它为合数
这个几乎不增加计算代价的操作提高了判定合数的概率。
\subsubsection{实现}

Expand Down
81 changes: 81 additions & 0 deletions Review/Other/TricksAndIdeas.tex
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ \subsubsection{奇偶排序优化}
胡小兔的良心莫队教程:莫队、带修改莫队、树上莫队 - 胡小兔
\url{https://www.cnblogs.com/RabbitHu/p/MoDuiTutorial.html}
}。
\subsubsection{回滚莫队}
回滚莫队适用于莫队的删除操作无法快速地更新答案的情况(比如维护最值,使用可删堆无法保证更新
复杂度)。既然删除操作不好处理,干脆仅使用加入操作维护当前区间。考虑朴素区间排序方式,在左
端点块标号相同的情况下,右端点单调递增,而左端点所在的块大小为$O(\sqrt{n})$。那么可以将
左端点所在块单独处理,块右边的端点移动仅有插入操作,总时间复杂度$O(n\sqrt{n})$。每次处理
完右边后,暴力枚举查询区间落在左端点所在块内的元素尝试更新答案(更新次数最多为$O(\sqrt{n})$
对于维护最值问题可以将当前答案单独存储而不是全局修改,这样就不会影响到下一次查询),然后
消除这些元素的影响(回滚莫队使其不影响最值的维护,其它数据很容易维护)。对于左右端点在同一块内
的特殊情况,由于块大小为$O(\sqrt{n})$,直接暴力维护。

\paragraph{例题 「JOISC 2014 Day1」 历史研究}
回滚莫队经典题,参考代码:

\lstinputlisting{Source/Source/Block/LOJ2874.cpp}

上述内容参考了yashem66的博客\footnote{
BZOJ4241 历史研究 (分块 回滚莫队-教程向)
\url{https://blog.csdn.net/qq\_33330876/article/details/73522230}
}。
\subsection{分块}\label{dividing}
\subsubsection{序列分块}
一般将序列分为多块,维护块内信息,区间查询/修改时整块处理,左右剩余元素暴力。
Expand Down Expand Up @@ -196,6 +215,59 @@ \subsection{优先队列维护长序列}\label{PQS}

有时甚至可以直接使用预排序+队列来代替优先队列(即$a\rightarrow a',b\rightarrow b',
a<b\Rightarrow a'<b'$时)。

更加形式化的描述:这些方案构造出了一个DAG,如果某个方案被选中,它的前驱必定被选中。
每次选取一个方案后将它的后继加入优先队列。如果这个DAG是一棵树,就不需要去重,最好构造
出可树型转移的方案表示。

\subsubsection{集合选数最值问题}
\paragraph{类型一}
给定$n$个可重集合,在每个集合中选取一个数,求这些选择方案中数的和(积)前$k$大(小)值。

$n=2$时,沿用上面的做法可以解决。但是当方案的维数增加时,这种方法就不再有效了。
思考的方向仍然时构造一棵树,满足每个点只有一个前驱,以及较少的常数个后继。

首先DAG的根肯定是每个集合都贪心选择最大值。对每个集合内的元素排序,可以用
$(p_1,p_2,\cdots,p_n)$表示集合$i$选择第$p_i$个数,但是这样的状态不好转移,需要去重且
后继数过多。考虑当前变动集合$i$的选择,前面的集合全部固定,后面的集合全部取最大值。那么可以
使用状态$(i,j,s)$表示当前改变集合$i$,选择第$j$大的数,该方案的价值为$s$(这样就不必
存储前面集合的选择方案)。然后得到如下后继:
\begin{itemize}
\item 当前集合后移:若$j<|S_i|$,存在后继$(i,j+1,s-A[i][j]+A[i][j+1])$
\item 改变下一个集合:若$i<n,2\leq |S_{i+1}|$,存在后继
$(i+1,2,s-A[i+1][1]+A[i+1][2])$
\item 注意到变换下一个集合时当前集合选择的数肯定不是最大值,但存在这种方案。
所以指定当$j=2,i<n,2\leq |S_{i+1}|$时当前集合选择最大值,然后改变下一个集合。
即存在后继$(i+1,2,s-A[i][2]+A[i][1]-A[i+1][1]+A[i+1][2]$。此时并不能保证
其后继不大于前驱,对集合以$A[i][1]-A[i][2]$为关键字升序排序就可以解决这个问题。
\end{itemize}

这种状态设置方式保证了前驱大于等于后继且前驱唯一,同时后继数可控而少。

{\bfseries 注意如果以贪心选取最大值的方案为根,第三种后继的假设不成立,会出现重复方案。
因此要以状态$(1,1,s_2)$为DAG根,最大值单独统计。}

例题:第十三届北航程序设计竞赛预赛 M 最优卡组
\lstinputlisting{Source/Source/Greed/LOJ6254.cpp}

\paragraph{类型二}
给定一个可重集合,选择$m$个数作为一种方案,求价值前$k$大的方案。

同样使用按位更改的方法,不过要按顺序记录每个被选数的位置。这样的状态转移是一个DAG,
存在重复状态。可以再加一维$i$表示$i+1,\cdots m$的位置都被确定,当前状态转移时
要么将锁定位置前移并修改,要么修改位置$i$选择的数。

\paragraph{特殊情况}
若方案的价值定义为它们的积且存在负元素,可以考虑将正负数分开计算,然后枚举负数个数,
根据负数个数的奇偶性决定取负数积的绝对值的前$k$大/小值。最后使用类似双指针法根据计算
出的正负数两个堆得到所需答案。

例题:SGU 421 k-th Product

该内容参考了tkandi的博客\footnote{
集合选数最值一类问题
\url{https://www.cnblogs.com/tkandi/p/9375509.html}
}。
\subsection{可删堆}\label{MultiSet}
一个简单的方法是使用$std::multiset$,但是其常数很大;
更保险的做法是使用两个优先队列(已加入/已删除)来完成操作:
Expand Down Expand Up @@ -262,6 +334,12 @@ \subsection{cdq分治}\label{CDQ}
\subsection{Kernelization}
\index{*TODO!Kernelization}
参见\url{https://www.zhihu.com/question/272303098/answer/367368615}。
\subsection{启发式合并}
如果某个区间内需要的数据结构可以用子区间的数据结构合并,且规模与区间长度成正比,
就可以考虑使用启发式合并。每次合并时将较小的数据结构拆开,插入大的数据结构中。
每合并一次至少会使数据结构扩大一倍(针对较小数据结构而言),合并次数为$O(\lg n)$
插入次数为$O(n\lg n)$。此法适用于平衡树、链表等只支持单点插入的数据结构,线段树则
有另外一套简单的合并方法。
\subsection{启发式分治}
区间$[l,r]$是否符合条件与区间的特殊值相关,求符合条件的区间数。

Expand Down Expand Up @@ -316,6 +394,9 @@ \subsection{注意事项/常见转化/思想}
\item 最短路、网络流问题在时空复杂度无法承受时考虑动态加边。
\item 询问参数局部修改时考虑从上一个最优解开始迭代。
\item 区间范围没有保证$l\leq r$时要注意swap。
\item 节点到根的路径上的点权值+1,统计某个点的权值。\sout{树链剖分+区间修改单点查询。}
使用树上差分直接在该点修改,查询时查询子树权值和,可以$O(n)$预处理$O(1)$查询,比原方法
修改少$\lg^2 n$,查询少$\lg n$
\end{itemize}
\subsection{本节注记}
2013年许昊然的国家集训队论文答辩《浅谈数据结构题的几个非经典解法——<Claymore>命题报告》
Expand Down
10 changes: 6 additions & 4 deletions Review/Path/DCS.tex
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ \subsection{判断是否有可行解}
\begin{itemize}
\item DFS-SPFA:松弛时递归下去,当存在环时说明存在负权环。
\sout{使用setjmp/\\longjmp较为方便。}
\item BFS-SPFA:当某节点{\bfseries 入队次数}(注意不是被松弛次数)大于等于节点
总数时,存在负权环。
\item BFS-SPFA:当某节点{\bfseries 最短路点数}大于等于节点
总数时,存在负权环。使用最短路点数而不是入队次数判断可以避免某些SPFA优化
影响算法正确性。
\end{itemize}
一般使用DFS法(跑得飞快),但是如果不存在环,DFS法的效率会比BFS法慢,
所以可以使用卡时技巧将DFS/BFS结合。
一般使用DFS法(跑得飞快),根据姜碧野的论文《SPFA 算法的优化与应用》,还可以
使用贪心预处理(松弛1次就退出)与迭代加深法加速负环查找。注意将初始距离设为0并
不影响算法正确性,但要以每个点为起点DFS(负环上必有一条可以松弛的负权边)。

代码如下:
\lstinputlisting{Source/Templates/NegRing.cpp}
Expand Down
2 changes: 2 additions & 0 deletions Review/Path/SMSSP.tex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ \subsubsection{SLF+Swap}

更多优化与Hack参见\url{https://www.zhihu.com/question/292283275}。
\index{*TODO!卡SPFA}
\subsection{算法优化}
若用节点编号表示状态来求最短路,要具体分析题目,阻止无效状态入队。
\subsection{Dijkstra}
使用二叉堆的最坏时间复杂度$O((V+E)\lg V)$,在稀疏图中表现良好。
稠密图可以使用$O(V^2)$暴力。
Expand Down
13 changes: 11 additions & 2 deletions Review/String/SAM.tex
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ \subsubsection{计数问题}

状态$s$表示了$s.len-s.link.len$个本质不同的子串,每种子串有$s.right$个。

优化:拓扑排序时可以按照len进行分层基数排序。
优化:拓扑排序时可以按照len进行分层基数排序,但是广义SAM不能使用这种方法
\subsubsection{可持久化线段树合并维护Right集合}
有时需要判定某个终点是否在某个状态的Right集合内,可以在extend时给新建状态添加
对应的Right值(不需要太严格,子串右节点也行),然后拓扑排序进行线段树合并计算出
Expand Down Expand Up @@ -136,12 +136,21 @@ \subsection{广义SAM}
时间复杂度为O(Trie大小*字符集大小)。
\end{itemize}

\subsubsection{统计状态对应的模板串数}
为每个节点记录最后匹配的模板串编号,每次extend后从$last$开始沿着parent树暴力上跳,
将这些节点的$count$值加一,直到遇到被同模板串标记过节点的为止。最坏时间复杂度
$O(S\sqrt{2S})$,一般情况下不会达到最坏情况,有时跑得比$\lg$做法还快。在想不出
更优做法时是这一个简单有效的方法。

以上内容参考了WC2012陈立杰的讲课课件《后缀自动机 Suffix Automaton》
与Candy?\footnote{[后缀自动机]【学习笔记】
\url{https://www.cnblogs.com/candy99/p/6374177.html}
}和dwjshift\footnote{
}、dwjshift\footnote{
用SAM建广义后缀树 $\ll$ dwjshift's Blog
\url{http://dwjshift.logdown.com/posts/304570}
}、Mangoyang\footnote{
一个用SAM维护多个串的根号特技
\url{https://www.cnblogs.com/mangoyang/p/10155185.html}
}的博客。Menci的博客写得更详细,一些性质的证明请移步
\url{https://oi.men.ci/suffix-automaton-notes/}。

Expand Down
81 changes: 81 additions & 0 deletions Source/ACM/BZOJ2780.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <cstdio>
#include <string>
std::string read() {
std::string str;
int c;
do
c = getchar();
while(c < 'a' || c > 'z');
while('a' <= c && c <= 'z') {
str.push_back(c);
c = getchar();
}
return str;
}
const int size = 360005;
struct Node {
int ch[26], fail;
} T[size];
int icnt = 0;
int insert() {
int c, p = 0;
do
c = getchar();
while(c < 'a' || c > 'z');
while('a' <= c && c <= 'z') {
int& v = T[p].ch[c - 'a'];
if(!v)
v = ++icnt;
p = v;
c = getchar();
}
return p;
}
int q[size];
void cook() {
int b = 0, e = 0;
for(int i = 0; i < 26; ++i)
if(T[0].ch[i])
q[e++] = T[0].ch[i];
while(b != e) {
int u = q[b++];
for(int i = 0; i < 26; ++i) {
int& v = T[u].ch[i];
if(v) {
T[v].fail = T[T[u].fail].ch[i];
q[e++] = v;
} else
v = T[T[u].fail].ch[i];
}
}
}
int cnt[size], last[size];
void match(int id, const std::string& str) {
int p = 0;
for(int i = 0; i < str.size(); ++i) {
int ch = str[i] - 'a';
p = T[p].ch[ch];
int cp = p;
while(cp) {
if(last[cp] != id)
last[cp] = id, ++cnt[cp];
cp = T[cp].fail;
}
}
}
int endPos[60005];
std::string P[10005];
int main() {
int n, q;
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; ++i)
P[i] = read();
for(int i = 1; i <= q; ++i)
endPos[i] = insert();
cook();
for(int i = 1; i <= n; ++i)
match(i, P[i]);
for(int i = 1; i <= q; ++i)
printf("%d\n", cnt[endPos[i]]);
return 0;
}
80 changes: 80 additions & 0 deletions Source/Block/LOJ2874.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
int read() {
int res = 0, c;
do
c = getchar();
while(c < '0' || c > '9');
while('0' <= c && c <= '9') {
res = res * 10 + c - '0';
c = getchar();
}
return res;
}
typedef long long Int64;
#define asInt64 static_cast<Int64>
const int size = 100005;
int A[size], B[size], C[size];
Int64 cans;
void add(int pos, Int64& ans) {
int col = A[pos];
Int64 val = asInt64(++C[col]) * B[col];
if(val > ans)
ans = val;
}
Int64 query(int l, int r) {
Int64 ans = cans;
for(int i = l; i <= r; ++i)
add(i, ans);
for(int i = l; i <= r; ++i)
--C[A[i]];
return ans;
}
struct Queue {
int l, r, bid, id;
bool operator<(const Queue& rhs) const {
return bid == rhs.bid ? r < rhs.r :
bid < rhs.bid;
}
} Q[size];
Int64 ans[size];
int main() {
int n = read();
int q = read();
for(int i = 1; i <= n; ++i)
A[i] = read();
memcpy(B + 1, A + 1, sizeof(int) * n);
std::sort(B + 1, B + n + 1);
int siz = std::unique(B + 1, B + n + 1) - (B + 1);
for(int i = 1; i <= n; ++i)
A[i] = std::lower_bound(B + 1, B + siz + 1,
A[i]) -
B;
int bsiz = sqrt(n) + 1;
for(int i = 1; i <= q; ++i) {
Q[i].l = read();
Q[i].r = read();
Q[i].id = i;
Q[i].bid = Q[i].l / bsiz;
}
std::sort(Q + 1, Q + q + 1);
Q[0].bid = -1;
int bend, cr;
for(int i = 1; i <= q; ++i) {
if(Q[i].bid != Q[i - 1].bid) {
memset(C + 1, 0, sizeof(int) * siz);
cans = 0;
cr = (Q[i].bid + 1) * bsiz;
bend = cr - 1;
}
while(cr <= Q[i].r)
add(cr++, cans);
ans[Q[i].id] =
query(Q[i].l, std::min(bend, Q[i].r));
}
for(int i = 1; i <= q; ++i)
printf("%lld\n", ans[i]);
return 0;
}
Loading

0 comments on commit 5964d85

Please sign in to comment.