Skip to content

Commit

Permalink
11.21
Browse files Browse the repository at this point in the history
  • Loading branch information
dtcxzyw committed Nov 21, 2018
1 parent b49ae56 commit 9def37c
Show file tree
Hide file tree
Showing 16 changed files with 259 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
"command": "g++",
"args": [
"-g",
"-O0",
"${file}",
"-o",
"bin/${fileBasenameNoExtension}.out",
"-Wall",
"-std=c++98",
"-D_FORTIFY_SOURCE=2",
"-fstack-protector-all",
"-fsanitize=address",
"-fsanitize=undefined"
],
"problemMatcher": {
"owner": "cpp",
Expand Down
1 change: 0 additions & 1 deletion Review/DP/DP.tex
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ \chapter{动态规划}
\input{DP/Slope}
\input{DP/Quad}
\input{DP/Power}

15 changes: 7 additions & 8 deletions Review/DP/Power.tex
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,25 @@ \subsection{常规矩阵快速幂}
\end{displaymath}
或对于图满足如下形式:
\begin{displaymath}
dp[d][i][j]=\sum_{(i,k)\in E,(k,j)\in E}{dp[d-1][i][k]*dp[d-1][k][j]}
dp[d][i][j]=\sum_{(i,k)\in E,(k,j)\in E}{dp[d-1][i][k]\cdot dp[d-1][k][j]}
\end{displaymath}
则可以使用矩阵快速幂优化。

建一个$k*k$的矩阵$A$,dp初始值为$1*k$的向量$v_0$
计算$k*k$的转移矩阵$A$,dp初始值为$1*k$的向量$v_0$
构造$A$,使其每乘一次$A$,向量表示的区间后移一格,那么
$A[i][j]$表示其在做一次乘法后将第$i$点的值贡献到第$j$点中的权值。

矩阵乘法满足结合律,可以使用快速幂进行计算
矩阵乘法满足结合律,因此可以使用矩阵快速幂进行计算

\subsection{特殊矩阵快速幂}

若大小为$n*n$的矩阵$A$可表示为大小为$n*k$的矩阵$B$与大小为
$k*n$的矩阵$C$的乘积,其中$k\ll n$
那么可以将$A$的幂拆开,错位结合,计算$k*k$的矩阵$D=CB$,对$D$快速幂后
计算需要的值{\bfseries尽量不要计算实际的转移矩阵)}。
计算需要的值{\bfseries答案向量为$v_0AD^{c-1}B$,尽量按需计算结果)}。

\paragraph{例题} bzoj3583 杰杰的女性朋友

使用上述方法优化矩阵快速幂效率。这里还存在一个问题,矩阵$A$$k$次幂求的是走$k$
步的方案数的转移矩阵,但是答案要的矩阵为矩阵的等比数列求和。因此我们
可以对于每次询问再加一个累加计数器,自己向自己连边,终点向自己连边,
最后单独求出起点到自己的方案数。
使用上述方法优化矩阵快速幂的效率。此外还存在一个问题,矩阵$A$$k$次幂求的是走$k$
步的方案数的转移矩阵,但是答案要的矩阵为矩阵幂求和。因此我们可以对于每次询问再加一个
累加计数器,自己向自己连边,对应点向自己连边,最后单独求出起点到自己的方案数。
6 changes: 3 additions & 3 deletions Review/DP/Quad.tex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
\section{四边形不等式优化}
在区间动规时通常会推出以下状态转移方程
在解区间动规题时通常会推出以下状态转移方程
\begin{displaymath}
dp[i][j]=min(dp[i][k]+dp[k+1][j])+w[i][j],i\leq k <j
\end{displaymath}
Expand All @@ -25,8 +25,8 @@ \section{四边形不等式优化}
dp[i][j]=min(dp[i][k]+dp[k+1][j])+w[i][j],s[i][j-1]\leq k \leq s[i+1][j]
\end{displaymath}

\paragraph{复杂度分析} 即对于每一个左端点,最多扫一遍右边所有点,
时间复杂度降为$O(n^2)$
\paragraph{复杂度分析} 对于每一个左端点,最多扫一遍右边所有点,
因此时间复杂度降为$O(n^2)$

\paragraph{优化} 计算$w[i][j]$时要考虑区间统计方面的优化(如前缀和)。

Expand Down
2 changes: 1 addition & 1 deletion Review/DP/Queue.tex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
\section{单调队列优化}
当状态转移方程为如下形式时
\begin{displaymath}
dp[i][j]=max(dp[i-1][j-k]+w(i,j,k))+c(i,j),l \leq k \leq r
dp[i][j]=max(dp[i-1][j-k]+w(i-1,j-k))+c(i,j),l \leq k \leq r
\end{displaymath}
可以使用单调队列优化。

Expand Down
26 changes: 13 additions & 13 deletions Review/DP/Slope.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,41 @@ \subsection{推导}
\begin{displaymath}
dp[i]=min(a[i]*b[j]+c[i]+d[j])
\end{displaymath}
使用斜率优化
考虑使用斜率优化

以下推导假设$a[i]$递减且$b[j]$递增。
以下推导假设$a[i]$单调递减且$b[j]$单调递增:

设决策点$j<k<i$,且从点$k$转移到$i$优于点$j$
易证从点$k$转移到$i+1$同样优于点$j$,这就是决策单调性
设决策点$j<k<i$,且从点$k$转移到$i$不差于从点$j$转移到$i$
易证从点$k$转移到$i+1$同样不差于从点$j$转移到$i+1$。称该性质为决策单调性

接下来考虑点$k$比点$j$更优的条件
接下来考虑点$k$不比点$j$更差的条件
\begin{eqnarray*}
a[i]*b[j]+c[i]+d[j]&\geq&a[i]*b[k]+c[i]+d[k]\\
\Leftrightarrow -a[i]&\geq&\frac{d[j]-d[k]}{b[j]-b[k]}
\Rightarrow -a[i]&\geq&\frac{d[k]-d[j]}{b[k]-b[j]}
\end{eqnarray*}

记斜率
\begin{displaymath}
slope(i,j)=\frac{d[i]-d[j]}{b[i]-b[j]}
slope(i,j)=\frac{d[j]-d[i]}{b[j]-b[i]}
\end{displaymath}

可以发现使用单调队列维护,记$q[b]$为队首,$q[e-1]$为队尾:
斜率可以使用单调队列维护,记$q[b]$为队首,$q[e-1]$为队尾:
\begin{itemize}
\item$-a[i]\geq slope(q[b],q[b+1])$,则表明$q[b+1]$$q[b]$更优
\item$-a[i]\geq slope(q[b],q[b+1])$,则表明$q[b+1]$不比$q[b]$更差
弹出$q[b]$
\item$slope(q[e-2],q[e-1])\geq slope(q[e-1],i)$,则说明若
$q[e-2]$被弹出,$q[e-1]$一定被弹出,所以$q[e-1]$无效,可以先弹出。
\end{itemize}

``''的角度理解,单调队列相当于维护了一个下凸壳
``''的角度理解,单调队列维护了一个下凸壳
\subsection{应用}
主要过程就是研究决策单调性满足的条件,然后选取适当的数据结构维护信息,快速dp。

实际应用中需注意以下几点:
\begin{itemize}
\item 比较斜率时尽量使用乘法
\item 若斜率单调,使用单调队列,否则使用二分法(使用类似线性规划的思路找到切点)。
\item 若横坐标单调,使用单调队列,否则使用平衡树维护凸壳。
\item 比较斜率时尽量使用乘法避免精度误差
\item $a[i]$单调,使用单调队列,否则使用二分法(使用类似线性规划的思路找到切点)。
\item $b[i]$单调,使用单调队列,否则使用平衡树维护凸壳。
\end{itemize}

以上内容参考了MashiroSky的博客\footnote{斜率优化学习笔记 - MashiroSky
Expand Down
2 changes: 1 addition & 1 deletion Review/Recommendation.tex
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ \section{优秀资料}
\item CodeForces优秀文章 \url{http://codeforces.com/blog/entry/57282}
\item E-Maxx算法合集英文版 \url{https://cp-algorithms.com/}
\item OI-Wiki \url{https://github.com/24OI/OI-wiki/}
\item 知乎上OI相关文章收藏 \url{https://www.zhihu.com/collection/213577780}
\item 知乎上OI相关文章 \url{https://www.zhihu.com/collection/213577780}
\end{itemize}
2 changes: 1 addition & 1 deletion Review/Tree/CBD.tex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
\section{链剖分}
以下方法只适用于静态树。
\subsection{轻重链剖分}
\subsection{轻重链剖分}\label{CBDW}
对于每个节点,选取其子树最大的儿子作为重儿子,其余节点为轻儿子。
然后将连续的重儿子串成一条链(DFS序上连续),每条链上最浅的节点为链头,
链内每个节点都指向链头方便跨越轻边跳跃到上一条链。
Expand Down
12 changes: 7 additions & 5 deletions Review/Tree/LCA.tex
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ \subsection{倍增法}
return p[u][0];
}
\end{lstlisting}
预处理$O(n\lg n)$查询$O(\lg n)$
预处理$O(n\lg n)$单次查询$O(\lg n)$
\subsection{树链剖分}
树链剖分内容参见第~\ref{CBDW}节。
树链剖分后,如果在同一条链上则返回较浅者,否则令链头较深的节点向上跳。

\begin{lstlisting}
Expand All @@ -49,11 +50,12 @@ \subsection{树链剖分}
\end{lstlisting}

两趟DFS预处理$O(n)$
由于树链剖分后重链不超过$\lg n$条,所以查询也是$O(\lg n)$的,但树链剖分的常数比倍增法小。
由于树链剖分后重链不超过$\lg n$条,所以单次查询也是$O(\lg n)$的,但树链剖分的常数比倍增法小。
\subsection{欧拉序+ST表}
考虑DFS序,显然两个来自节点$i$的不同子树的点的LCA为节点$i$,那么可以在
DFS完一棵子树后加入节点$i$的深度作为隔板,查询ST表上访问时间戳最小值。为了
应对点对在一条到根的链上的情况,同时也为了节点$i$一个时间戳,在遍历节点$i$之初就插入一个隔板。
DFS完一棵子树后加入节点$i$的深度作为隔板,维护ST表查询区间内深度最小的节点编号。为了
应对点对在一条到根的链上的情况,同时也为了节点$i$给一个访问时间戳,
在遍历节点$i$之初就插入一个隔板。
\begin{lstlisting}
int icnt = 0, A[size * 2][18], L[size], d[size];
void DFS(int u) {
Expand Down Expand Up @@ -91,7 +93,7 @@ \subsection{欧拉序+ST表}
}
\end{lstlisting}

预处理$O(n\lg n)$查询$O(1)$
预处理$O(n\lg n)$单次查询$O(1)$
\subsection{Tarjan}
当查询离线时,可使用Tarjan算法。

Expand Down
12 changes: 12 additions & 0 deletions Review/Tree/PBD.tex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ \subsubsection{重心性质}
\begin{property}
添加或减少一个叶子节点,重心最多偏移一条边。
\end{property}

\paragraph{树的同构判定}
对于一棵有根树可以使用一些Hash规则计算子树的Hash值(合并子树Hash值的运算需要满足交换律或
排序后合并,我的做法是计算多个满足交换律的运算合并值,然后把这些值胡乱混合)。以无根树
的重心(1-2个)为根,就可以计算整棵树的Hash值,用于树的同构判定。

{\bfseries 血泪史:存储当前最优点的数组大小要开$n$而不是2。尽管重心不超过2个,
但在处理过程中仍然存在超过2个的现行最远点。我在下面的板子中由于kp数组开小了而导致
last数组被覆写产生环,因此我调了一晚上。。。望引以为戒。}

\lstinputlisting{Source/Templates/TreeHash.cpp}

\subsubsection{重心选择}
以当前连通块内任意一点为根(当然是分治点的儿子)DFS,然后计算
其儿子的子树大小的最大值,然后与除自己子树外的节点数求最大值
Expand Down
Empty file added Source/DP/2317.cpp
Empty file.
18 changes: 18 additions & 0 deletions Source/Probability/1297.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <algorithm>
#include <cstdio>
int main() {
int n, A, B, C, a;
scanf("%d%d%d%d%d", &n, &A, &B, &C, &a);
double first = a % C + 1, last = a % C + 1,
res = 0.0;
for(int i = 2; i <= n; ++i) {
a = (static_cast<long long>(a) * A + B) %
100000001;
double cur = a % C + 1;
res += std::min(cur, last) / (cur * last);
last = cur;
}
res += std::min(first, last) / (first * last);
printf("%.3lf\n", res);
return 0;
}
107 changes: 107 additions & 0 deletions Source/TreeOfTree/1527.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include <algorithm>
#include <cstdio>
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;
}
const int size = 505, lsiz = size * size;
struct Node {
int l, r, siz;
} T[lsiz * 100];
int icnt = 0;
int insertImpl(int l, int r, int src, int p) {
int id = ++icnt;
T[id] = T[src];
++T[id].siz;
if(l != r) {
int m = (l + r) >> 1;
if(p <= m)
T[id].l = insertImpl(l, m, T[id].l, p);
else
T[id].r = insertImpl(m + 1, r, T[id].r, p);
}
return id;
}
int res, nl, nr;
void queryImpl(int l, int r, int a, int b) {
int delta = T[b].siz - T[a].siz;
if(delta == 0)
return;
if(nl <= l && r <= nr)
res += delta;
else {
int m = (l + r) >> 1;
if(nl <= m)
queryImpl(l, m, T[a].l, T[b].l);
if(m < nr)
queryImpl(m + 1, r, T[a].r, T[b].r);
}
}
int root[size][size], n, siz;
void insert(int x, int y, int val) {
while(x <= n) {
root[x][y] =
insertImpl(1, siz, root[x][y], val);
x += x & -x;
}
}
int query(int x, int a, int b) {
res = 0;
while(x) {
queryImpl(1, siz, root[x][a], root[x][b]);
x -= x & -x;
}
return res;
}
int bx, ex, by, ey;
int queryRange(int lv, int rv) {
nl = lv, nr = rv;
return query(ex, by - 1, ey) -
query(bx - 1, by - 1, ey);
}
int solve(int l, int r, int k) {
if(l == r)
return l;
else {
int m = (l + r) >> 1;
int lsiz = queryRange(l, m);
if(lsiz >= k)
return solve(l, m, k);
return solve(m + 1, r, k - lsiz);
}
}
int A[size][size], B[lsiz];
int main() {
n = read();
int q = read();
siz = 0;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
A[i][j] = B[++siz] = read();
std::sort(B + 1, B + siz + 1);
siz = std::unique(B + 1, B + siz + 1) - (B + 1);
for(int i = 1; i <= n; ++i)
for(int j = n; j >= 1; --j) {
root[j][i] = root[j][i - 1];
int pos =
std::lower_bound(B + 1, B + siz + 1,
A[j][i]) -
B;
insert(j, i, pos);
}
while(q--) {
bx = read();
by = read();
ex = read();
ey = read();
printf("%d\n", B[solve(1, siz, read())]);
}
return 0;
}
Loading

0 comments on commit 9def37c

Please sign in to comment.