<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>OI on Zirnc's Blog</title><link>https://blog.chungzh.cn/blog/oi/</link><description>Recent content in OI on Zirnc's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 29 Jul 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.chungzh.cn/blog/oi/index.xml" rel="self" type="application/rss+xml"/><item><title>网络流笔记</title><link>https://blog.chungzh.cn/oi-history/flow/</link><pubDate>Sat, 29 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/flow/</guid><description>最大流 概念 容量：$capacity(e)$ 表示一条有向边 $e(u, v)$ 的最大允许的流量。 流量：$flow(e)$ 表示一条有向边 $e(u, v)$ 总容量中已被占用的流量。 剩余流量（残量）：$w(e) = c(e)-f(e)$，表示当前时刻某条有向边 $e(u, v)$ 总流量中未被占用的部分。 反向边：原图中每一条有向边在残量网络中都有对应的反向边，反向边的容量为 $0$，容量的变化与原边相反；『反向边』的概念是相对的，即一条边的反向边的反向边是它本身。 残量网络：在原图的基础之上，添加每条边对应的反向边，并储存每条边的当前流量。残量网络会在算法进行的过程中被修改。 增广路（augmenting path）：残量网络中从源点到汇点的一条路径，增广路上所有边中最小的剩余容量为增广流量。 增广（augmenting）：在残量网络中寻找一条增广路，并将增广路上所有边的流量加上增广流量的过程。 层次：$level(u)$ 表示节点 $u$ 在层次图中与源点的距离。 层次图：在原残量网络中按照每个节点的层次来分层，只保留相邻两层的节点的图，满载（即流量等于容量）的边不存在于层次图中。 流的三个重要性质：
容量限制：对于每条边，流经该边的流量不得超过该边的容量，即，$f(u,v)\leq c(u,v)$ 斜对称性：每条边的流量与其相反边的流量之和为 0，即 $f(u,v)=-f(v,u)$ 流守恒性：从源点流出的流量等于汇点流入的流量，即 $\forall x\in V-{s,t},\sum_{(u,x)\in E}f(u,x)=\sum_{(x,v)\in E}f(x,v)$ 最大流问题：指定合适的流 $f$，以最大化整个网络的流量（即 $\sum_{(s,v)\in E}f(s,v)$）。
Ford-Fulkerson 增广 增广路指一条从 $s$ 到 $t$ 的路径，路径上每条边残余容量都为正。把残余容量为正（$w(u, v) \gt 0$）的边设为可行边，然后搜索寻找增广路。
找到一条增广路后，这条路能够增广的流值由路径上边的最小残留容量 $w(u, v)$（记为 $flow$）决定。将这条路径上每条边的 $w(u, v)$ 减去 $flow$。最后，路径上每条边的反向边残留容量要加上 $flow$。
为什么增广路径上每条边的反向边的残留容量值要加上 $flow$？因为斜对称性，残量网络=容量网络-流量网络，容量网络不变时，流量网络上的边的流量增加 $flow$，反向边流量减去 $flow$，残量网络就会发生相反的改变。从另一个角度来说，这个操作可以理解为「退流」，给了我们一个反悔的机会，让增广路的顺序不受限制。</description></item><item><title>网络流题集</title><link>https://blog.chungzh.cn/oi-history/flow-sol/</link><pubDate>Sat, 29 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/flow-sol/</guid><description>最大流 Luogu-P1231 教辅的组成 三倍经验： Luogu-P1402 酒店之王 / Luogu-P2891 [USACO07OPEN] Dining G
一眼丁真建图：S-&amp;gt;练习册-&amp;gt;书-&amp;gt;答案-&amp;gt;T
然而是错的。很明显，书有可能被多次匹配，与题意不符。
正确的建图：S-&amp;gt;练习册-&amp;gt;书（拆点）-&amp;gt;答案-&amp;gt;T
为什么中间层的书要拆点呢？因为一本书不能被重复选用。我们的目的是保证一本书流出的流量只能是 $1$。所以我们把每个代表书的点拆成两个点，左边的点和练习册连边，右边的点和答案连边；左右对应点之间也要连一条容量为 $1$ 的边。
Luogu-P2764 最小路径覆盖问题 定理：最小路径覆盖数=$|G|$-二分图最大匹配数
首先我们假设现在原图内每个点都是一条路径，此时最少路径数为 $n$。
考虑合并路径，当且仅当两条路径首尾相连的时候可以合并。
将点 $x$ 拆成出点 $x$ 和入点 $x+n$，当我们连接 $u, v$ 时，转化为连接 $u, v+n$。将 $S$ 与所有 $u$ 连边，将所有 $u+n$ 与 $T$ 连边。所有边的容量都为 $1$。
在一开始每个点都是一条独立的路径，每次合并将两条路径合并为一条路径，那么最终路径即为点数减去最大匹配数，这样求得的路径覆盖即为最小路径覆盖。
对于输出路径，用 to 记录下一个节点，tag 标记该节点前面是否还有点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ll dfs(int u, int t, ll flow) { if (u == t) return flow; ll ans = 0; vis[u] = 1; for (int &amp;amp;i = cur[u]; ~i; i = e[i].</description></item><item><title>CF-559C Gerald and Giant Chess</title><link>https://blog.chungzh.cn/oi-history/cf-559c/</link><pubDate>Sun, 21 May 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/cf-559c/</guid><description>CF-559C Gerald and Giant Chess / AtCoder DP-Y Grid 2
给定一个 $H*W$ 的棋盘，棋盘上只有 $N$ 个格子是黑色的，其他格子都是白色的。在棋盘左上角有一个卒，每一步可以向右或者向下移动一格，并且不能移动到黑色格子中。求这个卒从左上角移动到右下角，一共有多少种可能的路线。
$(1 ≤ h, w ≤ 105, 1 ≤ n ≤ 2000)$
$O(hw)$ 的暴力 DP 很好想，但是过不了。
假设没有障碍，从 $(1, 1)$ 到 $(i, j)$ 的方案数是 $C_{i+j-2}^{i-1}$（等于 $C_{i+j-2}^{j-1}$）。可以这么理解：可以用 $D, R$ 来表示一条路径，那么从 $(1, 1)$ 到 $(i, j)$ 的路径中有 $i-1$ 个 $D$ 和 $j-1$ 个 $R$。于是问题转化为从 $i+j-2$ 个位置中选 $i-1$ 个放 $D$ 的方案数。
如果有一个障碍，从正面统计方案数很困难，正难则反，考虑将总的方案数减去经过障碍的方案数。假设障碍的位置是 $(x, y)$，终点是 $(h, w)$，经过障碍的方案数就是 $C_{x+y-2}^{x-1} * C_{h-x+w-y}^{h-x}$（乘法原理）。</description></item><item><title>初等数论入门</title><link>https://blog.chungzh.cn/oi-history/number-theory-1/</link><pubDate>Fri, 05 May 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/number-theory-1/</guid><description>我也不知道这是从哪本书上抠来的？
整除 定义 1：如果 $a$ 和 $b$ 为整数且 $a \ne 0$，我们说 $a$ 整除 $b$ 是指存在整数 $c$ 使得 $b=ac$。如果 $a$ 整除 $b$，我们还称 $a$ 是 $b$ 的一个因子，且称 $b$ 是 $a$ 的倍数。
如果 $a$ 整除 $b$，则将其记为 $a \mid b$，如果 $a$ 不能整除 $b$，则记其为 $a \nmid b$。
定理 1：如果 $a, b$ 和 $c$ 是整数，且 $a \mid b, b \mid c$，则 $a \mid c$。
定理 2：如果 $a, b, m$ 和 $n$ 为整数，且 $c \mid a, c \mid b$，则 $c \mid (ma+nb)$。</description></item><item><title>Luogu-P4755 Beautiful Pair</title><link>https://blog.chungzh.cn/oi-history/luogu-p4755/</link><pubDate>Sun, 30 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/luogu-p4755/</guid><description>Luogu-P4755 Beautiful Pair
题意 小 D 有个数列 ${a}$，当一个数对 $(i,j)$（$i \le j$）满足 $a_i$ 和 $a_j$ 的积不大于 $a_i, a_{i+1}, \ldots, a_j$ 中的最大值时，小 D 认为这个数对是美丽的。请你求出美丽的数对的数量。
$1\le n\le{10}^5$，$1\le a_i\le{10}^9$。
编程时的问题 对 ST 表不熟悉！ 更 zz 的是，对 lower_bound 和 upper_bound 理解有问题，来复习一下小学知识：lower_bound 是找到“大于等于”的位置，upper_bound 是“大于”。写这道题的时候找小于某数的位置莫名其妙地用了 lower_bound，更没有 -1，完全是随手写的，半天也没察觉到这里有问题。 综上，我是 zz。
思路 考虑分治（据说这是套路），我们找出一个区间 $[l, r]$ 内的最大值位置 $mid$，然后统计所有跨过 $mid$ 的答案，再递归处理 $[l, mid-1], [mid+1, r]$。假设 $mid$ 左边的数是 $a_i$，右边的数是 $a_j$，根据题目得 $a_i * a_j \le a_{mid}$，即 $a_j \le \lfloor\frac{a_{mid}}{a_i}\rfloor$。那么我们枚举 $a_i$，然后用主席树统计右区间内小于 $\lfloor\frac{a_{mid}}{a_i}\rfloor$ 的数的个数。</description></item><item><title>ABC209F Deforestation</title><link>https://blog.chungzh.cn/oi-history/abc-209f/</link><pubDate>Fri, 28 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/abc-209f/</guid><description>ABC209F Deforestation
题意：给出 $n$ 棵树的高度，砍第 $i$ 棵树的花费是 $h_i+h_{i-1}+h_{i+1}$，求有多少种方案能使得砍完所有树的总代价最小。
砍一棵树的代价只与相邻的树高度有关。下面研究砍 $h_i$ 与 $h_{i+1}$ 的先后顺序对答案的影响。
先砍 $h_i$ 后砍 $h_{i+1}$：$h_i+h_{i-1}+h_{i+1}+h_{i+1}+h_{i+2}$ 先砍 $h_{i+1}$ 后砍 $h_i$：$h_{i+1}+h_i+h_{i+2}+h_i+h_{i-1}$ 作差后得到：$h_{i+1}-h_i$。当 $h_{i+1}&amp;gt;h_i$ 时，应该先砍 $h_{i+1}$。当 $h_{i+1}&amp;lt;h_i$ 时，应该先砍 $h_i$。因此，对于相邻的两棵树，先砍高的那棵最优。
插入 DP（insertion DP）：先考虑排好前 $i-1$ 个数，再往中间插入第 $i$ 个数。
令 $\mathit{f}_{i,j}$ 表示排好了前 $i$ 棵树的砍树次序，且第 $i$ 棵树排在第 $j$ 位，得到最小代价的方案数。
当 $h_{i+1} &amp;gt; h_i$ 时，应先砍 $i+1$，那么 $\mathit{f}{i+1,j} = \sum{k=j}^{i}\mathit{f}_{i,k}$
当 $h_i &amp;gt; h_{i+1}$ 时，应先砍 $i$，那么 $\mathit{f}{i+1,j} = \sum{k=1}^{j-1}\mathit{f}_{i,k}$
当 $h_i = h_{i+1}$ 时，砍哪棵都可以，$\mathit{f}{i+1,j} = \sum{k=1}^i\mathit{f}_{i,k}$</description></item><item><title>CDQ 分治笔记</title><link>https://blog.chungzh.cn/oi-history/cdq-divide/</link><pubDate>Fri, 28 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/cdq-divide/</guid><description>基本思想 CDQ 分治的基本思想十分简单。如下：
我们要解决一系列问题，这些问题一般包含修改和查询操作，可以把这些问题排成一个序列，用一个区间 $[L,R]$ 表示。 分。递归处理左边区间 $[L,M]$ 和右边区间 $[M+1,R]$ 的问题。 治。合并两个子问题，同时考虑到 $[L,M]$ 内的修改对 $[M+1,R]$ 内的查询产生的影响。即，用左边的子问题帮助解决右边的子问题。 这就是 CDQ 分治的基本思想。和普通分治不同的地方在于，普通分治在合并两个子问题的过程中，$[L,M]$ 内的问题不会对 $[M+1,R]$ 内的问题产生影响。
前置知识：二维偏序 给定 $N$ 个有序对 $(a,b)$，求对于每个 $(a,b)$，满足 $a2&amp;lt;a$ 且 $b2&amp;lt;b$ 的有序对 $(a2,b2)$ 有多少个。
可以将归并排序求逆序对的思路套用过来，这题实际上就是求顺序对。首先根据 $a$ 的大小排序，然后归并排序 $b$，这样就可以忽略 $a$ 元素的影响，因为左边区间的元素的 $a$ 一定小于右边元素的 $a$。归并排序时，每次从右边区间的有序序列取一个元素，然后求左边区间多少个元素比它小即可。
更浅显的解法是，用树状数组代替 CDQ 分治。这里就不赘述。
放个求逆序对的代码：
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void mergesort(int l, int r) { if (l &amp;gt;= r) return ; int mid = (l+r)/2; mergesort(l, mid); mergesort(mid+1, r); int lp = l, rp = mid+1; int i = l; while (lp &amp;lt;= mid &amp;amp;&amp;amp; rp &amp;lt;= r) { if (a[lp] &amp;gt; a[rp]) { ans += mid-lp+1; b[i++] = a[rp++]; } else { b[i++] = a[lp++]; } } while (lp &amp;lt;= mid) b[i++] = a[lp++]; while (rp &amp;lt;= r) b[i++] = a[rp++]; for (int i = l; i &amp;lt;= r; i++) a[i] = b[i]; } 例题 一：三维偏序 Luogu-P3810 【模板】三维偏序（陌上花开）</description></item><item><title>高斯消元笔记</title><link>https://blog.chungzh.cn/oi-history/gauss/</link><pubDate>Fri, 28 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/gauss/</guid><description>消元法及高斯消元法思想 消元法是将方程组中的一方程的未知数用含有另一未知数的代数式表示，并将其带入到另一方程中，这就消去了一未知数，得到一解；或将方程组中的一方程倍乘某个常数加到另外一方程中去，也可达到消去一未知数的目的。消元法主要用于二元一次方程组的求解。
消元法理论的核心 消元法理论的核心主要如下：
两方程互换，解不变； 一方程乘以非零数 $k$，解不变； 一方程乘以数 $k$ 加上另一方程，解不变。 过程 解方程组：
$$ \begin{cases} 2x_1+x_2-x_3=8 \ -3x_1-x_2+2x_3=-11 \ -2x_1+x_2+2x_3=-3 \end{cases} $$
写成矩阵的形式为：
$$ \left[\begin{matrix} 2 &amp;amp; 1 &amp;amp; -1 \ -3 &amp;amp; -1 &amp;amp; 2 \ -2 &amp;amp; 1 &amp;amp; 2 \end{matrix} \middle| \begin{matrix} 8 \ -11 \ -3 \end{matrix} \right] $$
这种矩阵称为增广矩阵。所谓增广矩阵，即为方程组系数矩阵 $A$ 与常数列 $b$ 的并生成的新矩阵，即 $(A | b)$，增广矩阵行初等变换化为行最简形，即是利用了高斯消元法的思想理念，省略了变量而用变量的系数位置表示变量，增广矩阵中用竖线隔开了系数矩阵和常数列，代表了等于符号。
我们从上到下依次处理每一行，处理完第 $i$ 行后，让 $A_{ii}$ 非 $0$，而 $A_{ji}(j\gt i)$ 均为 $0$。过程如下。</description></item><item><title>二分图笔记</title><link>https://blog.chungzh.cn/oi-history/bigraph/</link><pubDate>Sat, 15 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/bigraph/</guid><description>定义 在图论中，二分图（bipartite graph）是一类特殊的图，又称为二部图、偶图、双分图。二分图的顶点可以分成两个互斥的独立集 $U$ 和 $V$ 的图，使得所有边都是连结一个 $U$ 中的点和一个 $V$ 中的点。
给定一个二分图 $G$，在 $G$ 的一个子图 $M$ 中，$M$ 的边集中的任意两条边都没有共同的端点，则称 $M$ 是一个匹配。
最小点覆盖：选最少的点，满足每条边至少有一个端点被选。
交错路始于非匹配点且由匹配边与非匹配边交错而成。
增广路是始于非匹配点且终于非匹配点的交错路。
特性 二分图中不存在奇环
因为每一条边都是从一个集合走到另一个集合，只有走偶数次才可能回到同一个集合。
König 定理：一个图是二分图当且仅当它的最小顶点覆盖的顶点数等于最大匹配的边数
首先，最小点集覆盖一定 &amp;gt;= 最大匹配，因为假设最大匹配为 $n$，那么我们就得到了 $n$ 条互不相邻的边，光覆盖这些边就要用到 $n$ 个点。现在我们来思考为什么最小点击覆盖一定 &amp;lt;= 最大匹配。任何一种 $n$ 个点的最小点击覆盖，一定可以转化成一个 $n$ 的最大匹配。因为最小点集覆盖中的每个点都能找到至少一条只有一个端点在点集中的边（如果找不到则说明该点所有的边的另外一个端点都被覆盖，所以该点则没必要被覆盖，和它在最小点集覆盖中相矛盾），只要每个端点都选择一个这样的边，就必然能转化为一个匹配数与点集覆盖的点数相等的匹配方案。所以最大匹配至少为最小点集覆盖数，即最小点击覆盖一定 &amp;lt;= 最大匹配。综上，二者相等。
二分图判定 染色法：用 $1,2$ 两种颜色标记图中的节点，与一个节点相邻的所有节点的颜色必须和它不同，若标记过程中出现冲突，说明图中存在奇环。使用 DFS 实现。$O(N+M)$。
CF687A NP-Hard Problem 二分图判定裸题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include &amp;lt;bits/stdc++.</description></item><item><title>ABC133F Colorful Tree</title><link>https://blog.chungzh.cn/oi-history/abc-133f/</link><pubDate>Fri, 14 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/abc-133f/</guid><description>F - Colorful Tree
题意 有一个 $N$ 个节点的树，每条边有颜色、边权。
您需要处理 $Q$ 个询问，每个询问给出 $x_i,y_i,u_i,v_i$，您需要求出假定所有颜色为 $x_i$ 的边边权全部变成 $y_i$ 后，$u_i$ 和 $v_i$ 之间的距离。询问之间互相独立。
分析 DFS 序的思想套上主席树，root[i] 的权值线段树存从根到 $i$ 结点的每种颜色的边数（$cnt$），以及该颜色的长度和（$sum$）。顺便记录从根到 $i$ 结点的距离。利用差分，$dis(i, j) = dis(root, i)+dis(root, j)-2dis(root, LCA(i, j)$。然后 $i, j$ 的路径中该颜色的长度和也用同样的方法求出。那么答案就是 $dis(i, j) - \text{该颜色的长度和} + \text{该颜色的边数}*y$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include &amp;lt;bits/stdc++.</description></item><item><title>欧拉函数笔记</title><link>https://blog.chungzh.cn/oi-history/euler/</link><pubDate>Sat, 01 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/euler/</guid><description>定义 欧拉函数 $\varphi(n)$ 表示小于等于 $n$，且与 $n$ 互质的正整数的个数。
如何求 $\varphi(n)$？
比如 $varphi(12)$ 把 $12$ 质因数分解，$12=2^2*3$，其实就是得到了 $2$ 和 $3$ 两个互异的质因子。
然后把 $2$ 的倍数和 $3$ 的倍数都删掉。
$2$ 的倍数：$2,4,6,8,10,12$
$3$ 的倍数：$3,6,9,12$
但是是 $6$ 和 $12$ 重复减了。所以还要把既是 $2$ 的倍数又是 $3$ 的倍数的数加回来。所以这样写：$12 - 12/2 - 12/3 + 12/(2*3)$。运用了容斥原理。
性质 欧拉函数是积性函数。
积性是什么意思呢？如果有 $\gcd(a, b) = 1$，那么 $\varphi(a \times b) = \varphi(a) \times \varphi(b)$。
特别地，当 $n$ 是奇数时 $\varphi(2n) = \varphi(n)$。
$n = \sum_{d \mid n}{\varphi(d)}$。</description></item><item><title>欧拉回路笔记</title><link>https://blog.chungzh.cn/oi-history/euler-graph/</link><pubDate>Sat, 25 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/euler-graph/</guid><description>定义 欧拉回路：通过图中每条边恰好一次的回路 欧拉通路（欧拉路径）：通过图中每条边恰好一次的通路 欧拉图：具有欧拉回路的图 半欧拉图：具有欧拉通路但不具有欧拉回路的图 判定 无向图是欧拉图当且仅当： 非零度顶点是连通的 顶点的度数都是偶数 无向图是半欧拉图当且仅当： 非零度顶点是连通的 恰有 0 或 2 个奇度顶点 有向图是欧拉图当且仅当： 非零度顶点是强连通的 每个顶点的入度和出度相等 有向图是半欧拉图当且仅当： 非零度顶点是弱连通的 至多一个顶点的出度与入度之差为 1 至多一个顶点的入度与出度之差为 1 其他顶点的入度和出度相等 弱连通：将所有有向边替换为无向边后，整张图连通。
Hierholzer 算法 Hierholzer 算法的具体步骤：遍历当前节点的所有出边，并 DFS 访问相邻顶点，将经过的边删掉。遍历完所有出边后，将 $u$ 加入栈中。最后把栈中的顶点反过来，再输出，就是欧拉回路。
如果要求字典序最小，只需在一开始对每个点的所有出边从小到大排序。这样一来，欧拉回路上从左往右看，每个点的后继都取到了理论最小值。
对于无向图和有向图的欧拉路径，必须从奇点或唯一的出度比入度大 1 的点开始 dfs。
Luogu-P7771 【模板】欧拉路径
实现时，要注意一个小细节：用 hd[x] 数组记录节点 $x$ 目前删到了哪条边，每次走过一条边时要 hd[x]++，然后下次到达这个点时再调用这个值。否则的话，每次到了这个点都要从 0 开始判断这些边是否被删除掉，会超时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include &amp;lt;bits/stdc++.</description></item><item><title>割点和桥笔记</title><link>https://blog.chungzh.cn/oi-history/cut/</link><pubDate>Sun, 19 Mar 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/cut/</guid><description>定义 若对于无向连通图的一个点 $x$，从图中删去这个点和与这个点相连的所有边后，图不再是连通图，则 $x$ 为这个图的割点。
若对于无向连通图的一条边 $e$，从图中删去这条边后，图不再是连通图，则 $e$ 为这个图的割边（桥）。
求解 无向图的搜索树 从任意一个点出发进行 DFS，每个点只能访问一次，所有被访问过的结点和边构成一棵搜索树。
然后就可以将图上的边分为两类，树边和返祖边，返祖边连接了一个点和它的一个祖先。
时间戳 dfn 和追溯值 low $dfn[x]$ 表示在 DFS 的过程中，$x$ 第一次被访问的顺序。
$low[x]$ 表示 $x$ 和 $x$ 的子树中所有点的时间戳 和 从 $x$ 的子树中的点通过仅一条返祖边可以达到的点的时间戳 的最小值。
更新 $low$ 的方法：
如果 $v$ 是 $u$ 的儿子：$low[u] = min(low[u], low[v])$ 否则：$low[u] = min(low[u], dfn[v])$ 割点的判定 对于某个点 $u$，如果它的儿子中存在一个点 $v$，使得 $low[v] \ge dfn[u]$，即不能回到祖先，那么 $u$ 就是割点。
对于搜索树的根节点就比较特殊，如果它在搜索树中只有一个儿子，是不能成为割点的，需要特判。
树的叶子节点由于没有儿子，也不能成为割点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void tarjan(int u, int father) { int child = 0; vis[u] = 1; low[u] = dfn[u] = ++inde; for (int i = 0; i &amp;lt; g[u].</description></item><item><title>树链剖分笔记</title><link>https://blog.chungzh.cn/oi-history/hld/</link><pubDate>Sat, 25 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/hld/</guid><description>树链剖分（本文仅介绍 重链剖分（Heavy-Light Decomposition））的用途：
更新树上两点之间的路径上的所有点的值 求树上两点之间的路径上的最大值、最小值、和（或任意满足结合律的运算） 思想 树链剖分即把整棵树剖分成若干条链，然后用线段树等数据结构来维护链上的信息。重链剖分可以将树上的任何一条路径划分成不超过 $O(\log n)$ 条连续的链。
定义：
重子节点 表示其子节点中子树最大的子结点。如果有多个子树最大的子结点，取其一。如果没有子节点，就无重子节点。 轻子节点 表示剩余的所有子结点。 从这个结点到重子节点的边为 重边。 到其他轻子节点的边为 轻边。 若干条首尾衔接的重边构成 重链。 把落单的结点也当作重链，那么整棵树就被剖分成若干条重链。
性质 树上每个节点都属于且仅属于一条重链。
重链开头的结点不一定是重子节点（因为重边是对于每一个结点都有定义的）。
所有的重链将整棵树 完全剖分。
在剖分时 重边优先遍历，最后树的 DFN 序上，重链内的 DFN 序是连续的。按 DFN 排序后的序列即为剖分后的链。
一颗子树内的 DFN 序是连续的。
可以发现，当我们向下经过一条 轻边 时，所在子树的大小至少会除以二。
因此，对于树上的任意一条路径，把它拆分成从 $lca$ 分别向两边往下走，分别最多走 $O(\log n)$ 次，因此，树上的每条路径都可以被拆分成不超过 $O(\log n)$ 条重链。
实现 模板 RECORD。</description></item><item><title>CSP-J/S2022 题解与反思</title><link>https://blog.chungzh.cn/oi-history/csp2022-sol/</link><pubDate>Sat, 05 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/csp2022-sol/</guid><description>学校 OI 停（tui yi）了，周末有空的时候补补。
J T1 乘方 Luogu-P8813 [CSP-J 2022] 乘方
如果 $a^b$ 的值不超过 ${10}^9$，则输出 $a^b$ 的值，否则输出 -1。数据范围：$1 \le a, b \le {10}^9$。
$2^{30}=1073741824 &amp;gt; 10^9$，所以循环最多 29 次就能判断是否超过 $10^9$。注意 $1$ 的任何次幂都是 $1$，不能进行循环，特判一下即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include &amp;lt;bits/stdc++.h&amp;gt;using namespace std; const long long MAXX = 1e9; int main() { long long a, b; cin &amp;gt;&amp;gt; a &amp;gt;&amp;gt; b; if (a == 1) { cout &amp;lt;&amp;lt; 1 &amp;lt;&amp;lt; endl; } else { long long s = 1; bool flag = 0; for (long long i = 1; i &amp;lt;= b &amp;amp;&amp;amp; s*a &amp;lt;= MAXX; i++) { s *= a; if (i == b) { flag = 1; } } if (!</description></item><item><title>ARC101B Median of Medians - 中位数</title><link>https://blog.chungzh.cn/oi-history/arc-101b/</link><pubDate>Sun, 23 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/arc-101b/</guid><description>D - Median of Medians
题意 给定一个整数序列 $a[1],a[2],&amp;hellip;.a[n]$，那么对于 $a$ 序列的任意一个连续子序列 $a[L],a[L+1],&amp;hellip;&amp;hellip;a[R]$，其中 $1&amp;lt;=L&amp;lt;=R&amp;lt;=n$, 求出该连续子序列的中位数，记为 $b[L][R]$。
显然 $b$ 数组共有 $n*(n+1)/2$ 个整数。
输出 $b$ 数组的中位数。
分析 关于中位数有一个 Trick：
我们二分一个数 $mid$，对于原序列中 $\ge mid$ 的数，我们标记为 $1$；反之，对于 $&amp;lt; mid$ 的数，我们标记为 $−1$。 标记结束后，如果一个区间内的标记和大于等于 $0$，说明中位数大于等于 $mid$，那么向右二分；反之向左。 对于本题，我们对 $b$ 数组二分它的中位数 $mid$，并按 $mid$ 对 $a$ 数组进行 $+1,-1$ 标记。然后问题就变为了：统计有多少个区间的标记和 $\ge 0$。
记这个区间数为 $cnt$，若 $cnt\ge \lfloor \frac{n(n+1)/2+1}{2} \rfloor$，说明 $b$ 数组实际中位数 $\ge mid$，向右二分。否则向左二分。
怎么求有多少个区间的标记和 $\ge 0$ 呢？我们可以做一个前缀和 $s$，统计 $i &amp;lt; j$ 且 $s[i] \le s[j]$ 的个数。这是一个二维偏序问题，可以搭配树状数组解决。</description></item><item><title>CF-342E Xenia and Tree - 根号分治</title><link>https://blog.chungzh.cn/oi-history/cf-342e/</link><pubDate>Thu, 06 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/cf-342e/</guid><description>CF342E Xenia and Tree
题意 给定一棵 $n$ 个节点的树，初始时 1 号节点为红色，其余为蓝色。
要求支持如下操作：
将一个节点变为红色。 询问节点 $u$ 到最近红色节点的距离。 共 $q$ 次操作。
$1 \le n, q \le 10 ^5$
分析 首先我们有两种暴力思路：
每次将一个点变为红色，就从那个点开始 BFS，更新它周边结点的最小值，直到无法更新。 每次询问，都和之前的红色点求 LCA，计算出距离，再取最小值。 这两种做法都过不了。但我们可以将它们结合起来，这就是根号分治（a.k.a. 操作分块）。
我们把操作序列以 $\sqrt m$ 为块长分块，对于一个询问，有两种情况：
在同一块内且在询问之前的修改，可以暴力 LCA 求距离。 对于之前块的修改，可以在处理完那个块之后，从块中修改的红点开始多源 BFS 更新每个点的答案。 最后答案便是两种情况取最小值。
大概也许是 $O((n+m)\sqrt m)$？其实我不会算，但是挺快的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include &amp;lt;bits/stdc++.</description></item><item><title>最短路笔记</title><link>https://blog.chungzh.cn/oi-history/shortest-path/</link><pubDate>Sat, 01 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/shortest-path/</guid><description>又是一年 CSP 复赛，已经一年没写过最短路了，赶紧复习一下。
Floyd-Warshall 算法 Floyd 算法是用来求所有结点对最短路的。适用于所有不含负环的图。
这个算法运用了 DP 的思想。首先定义 f[k][i][j] 表示只允许经过结点 $1, 2, \cdots k$，结点 $i$ 到结点 $j$ 的最短路长度。初始化时，f[k][i][i] = 0，其他赋值为 $+\infty$。可以有 f[k][i][j] = min(f[k-1][i][j], f[k-1][i][k] + f[k-1][k][j])（f[k-1][i][j] 表示不经过 $k$ 点的最短路径，f[k-1][i][k] + f[k-1][k][j] 表示经过 $k$ 点的最短路径）。这时可以发现，数组的第一维是可以忽略的，所以直接写成 f[i][j] = min(f[i][j], f[i][k] + f[k][j])。
时间、空间复杂度均为 $O(N^3)$。
实现：
1 2 3 4 for (int k = 1; k &amp;lt;= n; k++) for (int i = 1; i &amp;lt;= n; i++) for (int j = 1; j &amp;lt;= n; j++) f[i][j] = min(f[i][j], f[i][k] + f[k][j]); 无向图的最小环问题 Luogu-P6175 无向图的最小环问题</description></item><item><title>CSP-J 2022 游记</title><link>https://blog.chungzh.cn/oi-history/csp2022/</link><pubDate>Sun, 25 Sep 2022 10:35:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/csp2022/</guid><description>CSP-J1/S1 这次和初一的时候一样，在自己学校考试，舒服。
感觉题目偏简单，但是出了很多奇怪的题目。。。(网上的 dalao 都说有一堆错误？
估分 J 组 90，S 组 68，应该也许能过吧。
结果 J 组 92，S 组 65.5。
GD 出分数线一如既往地咕咕咕咕咕。
本来 S 组应该才勉强压线（ZJ 64），但是今年 GD 貌似加了很多机位，分数线才 55。于是初三的我第一次进了 S 组复赛。。。
CSP-J2/S2 Day -? 本来说要去东莞考的，后来又改到石中了，有点失望。
Day 0 周五。全市封校。:) 上次被疫情搞得回不了家还是初一下学期的时候。
Day 1 早上五点四十起床，坐大巴去石中。
J 组，前两题大概花了一小时。T3 和 2020-T3 如出一辙，考前一晚看过，但是没看懂。于是考场上花了很久思考，最后想到一种实现方式，就没有思考过时间复杂度直接开写，结果大样例超时，一共花了一小时。最后花了十几分钟乱写 T4，总体自我感觉良好。
中午饭还算挺香，饭后在草丛里捡了个篮球，打了十几分钟，一个过路老师阴沉地说：“中午不准打球。”（鬼知道我在石中听到这句话多少次了。。。）
S 组，同桌是 czz 大神。慢慢地看 T1，发现还是没有头绪，打了个暴力。然后看到 T2，感觉有点希望，然后发现好水，赶紧做了，大喜。再看 T3，直接放弃。T4 再打了个暴力。这时候就到了六点，静坐了半小时，出考场。
考完之后感觉题简单了，运气好也许能冲 1=？
Day 2+ 洛谷自测 J 组，发现 T3 T4 各 50 分，网上都说 J 组很简单，感觉 1= 就这样没了。。。。</description></item><item><title>CF-1385E Directing Edges</title><link>https://blog.chungzh.cn/oi-history/cf-1385e/</link><pubDate>Fri, 26 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/cf-1385e/</guid><description>CF-1385E Directing Edges
题意 给定一个由有向边与无向边组成的图，现在需要你把所有的无向边变成有向边，使得形成的图中没有环。
如果可以做到请输出该图，否则直接输出&amp;quot;NO&amp;quot;。
分析 我们先只连接有向边，然后做一遍拓扑排序，如果失败了，就说明有环，输出 “NO”。
然后处理剩下的无向边。对于无向边 $(u, v)$，如果 $u$ 的拓扑序小于 $v$，那么令这条边的方向是 $u\rightarrow v$。否则，方向就是 $v\rightarrow u$。因为这条边是从拓扑序小的点指向拓扑序大的点，所以必然不会形成环。
RECORD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include &amp;lt;algorithm&amp;gt;#include &amp;lt;cstdio&amp;gt;#include &amp;lt;cstring&amp;gt;#include &amp;lt;iostream&amp;gt;#include &amp;lt;vector&amp;gt; using namespace std; const int MAXN = 200005; int n, m; vector&amp;lt;int&amp;gt; G[MAXN]; int c[MAXN], topo[MAXN], id[MAXN], t, bn, x[MAXN], y[MAXN]; bool dfs(int u) { c[u] = -1; for (auto v : G[u]) { if (c[v] &amp;lt; 0) return false; else if (c[v] == 0 &amp;amp;&amp;amp; !</description></item><item><title>拓扑排序笔记</title><link>https://blog.chungzh.cn/oi-history/topo-sort/</link><pubDate>Fri, 26 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/topo-sort/</guid><description>引入 给定一张有向无环图（DAG, Directed Acyclic Graph），对其顶点进行排序，使得对于每条从 $u$ 到 $v$ 的有向边 $(u, v)$，$u$ 在排序中都在 $v$ 的前面。这种排序就称为拓扑排序（Topological sorting）。
当且仅当图中没有定向环时（即有向无环图），才有可能进行拓扑排序。如果排序失败，就说明该有向图存在环，不是 DAG。
任何有向无环图至少有一个拓扑排序。
举例：在某校的选课系统中，存在这样的规则：每门课可能有若干门先修课，如果要修读某一门课，则必须要先修读此课程所要求的先修课后才能修读。假设一个学生同时只能修读一门课程，那么，被选课系统允许的他修完他需要所有课程的顺序是一个拓扑序。
在这个例子中，每一门课程对应有向图中的一个顶点，每一个先修关系对应一条有向边（从先修课指向需要先修课的课）。
算法 Kahn 算法 初始状态下，集合 $S$ 装着所有入度为 $0$ 的点，$L$ 是一个空列表。
每次从 $S$ 中任意取出一个点 $u$ 放入 $L$, 然后将 $u$ 的所有边 $(u, v_1), (u, v_2), (u, v_3) \cdots$ 删除。对于边 $(u, v)$，若将该边删除后点 $v$ 的入度变为 $0$，则将 $v$ 放入 $S$ 中。
不断重复以上过程，直到集合 $S$ 为空。检查图中是否存在任何边，如果有，那么这个图一定有环路，否则返回 $L$，$L$ 中顶点的顺序就是拓扑排序的结果。
基本上就是 BFS 的框架。
代码实现：
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int n, m; vector&amp;lt;int&amp;gt; G[MAXN]; int in[MAXN]; // 存储每个结点的入度 bool toposort() { vector&amp;lt;int&amp;gt; L; queue&amp;lt;int&amp;gt; S; for (int i = 1; i &amp;lt;= n; i++) if (in[i] == 0) S.</description></item><item><title>位运算笔记</title><link>https://blog.chungzh.cn/oi-history/bit-operation/</link><pubDate>Sun, 21 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/bit-operation/</guid><description>基本概念 比特（bit，亦称二进制位）是指 1 位二进制的数码（0 或 1），是计算机中信息的最小单位。
字节（byte）：一个字节由 8 位组成。
熟练地运用位运算，可以提高我们程序的时空效率。
计算机中的整数存储与运算 下面以 32 位二进制数，即 C++ 中的 int 和 unsigned int 类型为例。
原码、反码 简单介绍一下：
原码：最高位为符号位，正数为 $0$，负数为 $1$，其余所有位为十进制数的绝对值。
优点：对人类而言最直观。 缺点：无法将减法转换成加法运算。如：$1-1=1+(-1)=0001+1001=1010=-2$；$0$ 有两种表示方法 $0000$ 和 $1000$。 反码：最高位为符号位，正数为 $0$，负数为 $1$。正数的反码等于本身，负数的反码除符号位外，各位取反。
优点：解决了减法运算的问题。$1-1=1+(-1)=0001+1110=1111=0$ 缺点：$0$ 有两种表示方法 $0000$ 和 $1111$；减法算法规则较复杂，需要额外判断溢出。 补码 32 位无符号整数 unsigned int： 直接把这 32 位编码 $C$ 看作 32 位二进制数 $N$。
32 位有符号整数 int： 以最高位作为符号位，$0$ 表示非负数，$1$ 表示负数。</description></item><item><title>李超线段树笔记</title><link>https://blog.chungzh.cn/oi-history/li-chao-tree/</link><pubDate>Tue, 16 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/li-chao-tree/</guid><description>线段树之标记永久化 普通的线段树在做区间修改时依赖懒标记（lazy tag），当我们从一个点向下访问时，需要将标记 pushdown。能否避免如此多的 pushdown 操作呢？这时需要用到标记永久化技巧。
我们要做的就是将 lazy tag 永久地留在当前的结点，这时子树中的所有结点都不会被这个 tag 所影响。因此，子树中询问的最大值 = 实际最大值 - tag。当我们想得到正确答案时，只要将子树返回的最大值加上当前 tag 即可。
标记永久化存在局限性，需要满足不同的修改操作可以交换顺序，或者说对答案的贡献是独立的这一条件。
举个例子：区间设置+区间加法，先设置后加和先加后设置的结果是不一样的，因此不能交换顺序。如果使用标记永久化，就可能改变了这个顺序。
比如我们先设置后加，并且令设置的区间比加的区间大，因此加的 tag 在下方，设置的 tag 在上方。 根据前面的方法，我们会从下往上取 tag，也就是先加法，再设置。 这时我们发现，由于上层是一个设置操作，下面的所有答案最终都变成了设置的那个数字，下层操作就失效了。显然有问题。 总结：标记永久化就是不再下放标记，而是让标记永久地停留在当前结点上。在统计答案时再考虑标记的影响。
复杂度分析：由于标记不会下放，但如果有两个标记落在了一个结点上，我们不会分别存储这两个标记，而是加起来合成一个标记（$(+2) + (+3) = (+5)$）。因此，每个结点最多只有一个标记。询问时最多考虑 $\log n$ 个标记，复杂度和普通线段树相同 $O(\log n)$。
程序实现：
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int up(int p) { tree[p].mx = max(tree[p&amp;lt;&amp;lt;1].mx, tree[p&amp;lt;&amp;lt;1|1].mx)+tree[p].tag; } int query(int p, int l, int r, int x, int y) { if (l &amp;gt;= x &amp;amp;&amp;amp; r &amp;lt;= y) return tree[p].</description></item><item><title>树状数组笔记</title><link>https://blog.chungzh.cn/oi-history/binary-indexed-tree/</link><pubDate>Mon, 15 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/binary-indexed-tree/</guid><description>早就学习过线段树了，但惭愧的是更简单的树状数组却一直没有深入理解过，仅仅停留在背代码的层级。今天认真学习一下树状数组。
引入 树状数组（Binary Index Tree, BIT / Fenwick Tree）支持单点修改和区间查询两种简单操作，时间复杂度均为 $O(\log n)$。它的实现比线段树简单，速度更快，但功能稍逊一筹。
原理 我们用 $C_i$ 来表示 $A$ 数组的一段区间，定义 $x$ 的二进制表示中，最低位的 $1$ 的位置为 $\operatorname{lowbit}(x)$，那么用 $C_i$ 代表 $A$ 数组的下标区间 $[i-\operatorname{lowbit}(i)+1, i]$。举个例子，$4_{(10)} = 100_{(2)}$，$100_{(2)}-\operatorname{lowbit}(100_{(2)})+1_{(2)}=1_{(2)}=1_{(10)}$，那么 $C_4$ 代表的区间就是 $[1, 4]$。通过这样的设计，树状数组将结点数压缩到与数组长度相同，不像线段树一样需要 $2n$ 个结点。
之所以会有这个特点，是因为对于位置 $i$，其对应的结点所在的高度就是 $\operatorname{lowbit}(i)$ 的位数。第一层结点为全体 $2^0 + 2^1k$，即所有 $\operatorname{lowbit}(i)=1$ 的数字；第二层结点为全体 $2^1 + 2^2k$ ，即所有 $\operatorname{lowbit}(i)=2$ 的数字；第三层结点为全体 $2^2 + 2^3k$ ，即所有 $\operatorname{lowbit}(i)=4$ 的数字；以此类推。也就是说，对于位置 $i$，在这个位置往上垂直追溯，能追溯的层数就是 $i$ 的二进制表示的末尾 $0$ 数量。而结点高度又决定了其子树的大小，于是它所代表的信息区间大小也就一定是 $2^{i的末尾0数量}=\operatorname{lowbit}(i)$。
*来源于参考资料 1
实现 $\operatorname{lowbit}$ 如何计算呢？我们有这样一条公式：$\operatorname{lowbit}(x)=(x)&amp;amp;(-x)$。在计算机中，有符号数采用补码表示。在补码表示下，$x$ 的相反数 -x = ~x + 1，也就是按位非再加一。例如 $x$ 的最后一个 $1$ 的位置附近是 $\cdots 01000\cdots$，按位非之后是 $\cdots 10111\cdots$，加一再变成 $\cdots 11000\cdots$；而前面每一位都与原来相反。这时我们再把它和 $x$ 按位与，得到的结果为 $01000\cdots$ 即 $\operatorname{lowbit}(x)$。</description></item><item><title>斜率优化 DP 笔记</title><link>https://blog.chungzh.cn/oi-history/slope-opt-dp/</link><pubDate>Sat, 13 Aug 2022 21:59:00 -0800</pubDate><guid>https://blog.chungzh.cn/oi-history/slope-opt-dp/</guid><description>X(j) 和斜率均单调的斜率优化 这是第一次学斜率优化学会的。
例题 [HNOI2008]玩具装箱 Luogu LOJ
题目描述：
P 教授要去看奥运，但是他舍不下他的玩具，于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩，其可以将任意物品变成一堆，再放到一种特殊的一维容器中。
P 教授有编号为 $1 \cdots n$ 的 $n$ 件玩具，第 $i$ 件玩具经过压缩后的一维长度为 $C_i$。
为了方便整理，P 教授要求：
在一个一维容器中的玩具编号是连续的。
同时如果一个一维容器中有多个玩具，那么两件玩具之间要加入一个单位长度的填充物。形式地说，如果将第 $i$ 件玩具到第 $j$ 个玩具放到一个容器中，那么容器的长度将为 $x=j-i+\sum\limits_{k=i}^{j}C_k$。
制作容器的费用与容器的长度有关，根据教授研究，如果容器长度为 $x$，其制作费用为 $(x-L)^2$。其中 $L$ 是一个常量。P 教授不关心容器的数目，他可以制作出任意长度的容器，甚至超过 $L$。但他希望所有容器的总费用最小。
$1 \leq n \leq 5 \times 10^4$，$1 \leq L \leq 10^7$，$1 \leq C_i \leq 10^7$。
朴素 DP 做法 令状态 $f(i)$ 表示把前 $i$ 个玩具装箱的最小费用，$s(i)$ 为 $c_i$ 的前缀和。
假如将玩具 $j$ 到 $i$ 装在同一箱子，容易列出状态转移方程 $f(i) = \min_{1\le j\le i}{f(j-1)+(i-j+s(i)-s(j-1)-L)^2}$。</description></item><item><title>Luogu-P3521 「POI2011」ROT-Tree Rotations</title><link>https://blog.chungzh.cn/oi-history/luogu-p3521/</link><pubDate>Sun, 14 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/luogu-p3521/</guid><description>题意 给定一颗有 $n$ 个叶节点的二叉树。每个叶节点都有一个权值 $p_i$（注意，根不是叶节点），所有叶节点的权值构成了一个 $1 \sim n$ 的排列。
对于这棵二叉树的任何一个结点，保证其要么是叶节点，要么左右两个孩子都存在。
现在你可以任选一些节点，交换这些节点的左右子树。
在最终的树上，按照先序遍历遍历整棵树并依次写下遇到的叶结点的权值构成一个长度为 $n$ 的排列，你需要最小化这个排列的逆序对数。
$2 \leq n \leq 2 \times 10^5$， $0 \leq x \leq n$，所有叶节点的权值是一个 $1 \sim n$ 的排列。
分析 按照先序遍历整棵树，取叶结点，用人话说就是从左到右取叶子结点。
重要性质：交换了一个点的左右子树之后，不会影响左子树内和右子树内的逆序对数量。
考虑一个任意的结点，对它的子树中叶子的逆序对进行分类讨论：
都在左子树内； 都在右子树内； 跨越左右子树。 交换左右子树之后，受到影响的显然只有第三种情况。第一、第二种情况分治下去就可以转化成第三种再计算。
如何计算答案呢？一开始，对于每一个叶子结点，我们都建立一棵权值线段树（动态开点）并记录 $p_i$ 出现了 $1$ 次。合并 $r1, r2$ 时，逆序对的个数就是 $tree[rc[r1]] \times tree[lc[r2]]$。因为 $r1, r2$ 对应的权值区间 $[L, R]$ 是相同的，而 $lc[r1], lc[r2]$ 对应的权值区间就是 $[L, M]$， $rc[r1], rc[r2]$ 对应的权值区间是 $[M+1, R]$，$rc[]$ 中记录的数都比 $lc[]$ 中的要大，而 $r1$ 对应的数的编号小于 $r2$（也就是 $r1$ 是左子树，$r2$ 是右子树），满足逆序对的条件。</description></item><item><title>可持久化线段树笔记</title><link>https://blog.chungzh.cn/oi-history/persistent-seg-tree/</link><pubDate>Sun, 14 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/persistent-seg-tree/</guid><description>动态开点线段树 常规写法的线段树只能维护不算很长的数组，由于空间不够，对于 $10^9$ 级别的数组却不能很好地维护。所以，我们要用到动态开点线段树。
核心思想：节点只有在有需要的时候才被创建。
比如说，要求在一个长度为 $n &amp;lt; 10^9$ 的数组上实现区间求和、单点修改的操作，初始数组元素值均为 0。
那么，我们一开始只创建一个根结点，接下来遵循动态开点的核心思想进行操作。
比如下面这张图的例子，我们依次修改 1, 2, 8 三个结点，途中创建了必要的结点。而在图中没有显示的空结点并没有被创建，视为 0，这样就节省了空间。
那么对于区间修改时，会有 pushdown() 操作，可能会修改一个不存在的结点。这时有两个解决方案：
在 pushdown() 时，如果缺少孩子，就直接创建一个新的孩子就可以了。 使用 标记永久化 技巧（李超线段树），让结点不再进行 pushdown()，进一步节省了空间。 复杂度分析：单次操作的时间复杂度是不变的，为 $O(\log n)$。对于空间复杂度，由于每次操作都有可能创建并访问全新的一系列结点，因此 $m$ 次操作的空间复杂度是 $O(m\log n)$，不再是原本线段树的 $O(n)$。
代码实现：
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int n, cnt, root; // cnt 表示当前结点个数 int sum[N*2], ls[N*2], rs[N*2]; void upd(int&amp;amp; rt, int l, int r, int p, int f) { // 注意这里传入一个引用，可以修改 ls 或 rs 数组 if (!</description></item><item><title>线段树合并笔记</title><link>https://blog.chungzh.cn/oi-history/merge-seg-tree/</link><pubDate>Sun, 14 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/merge-seg-tree/</guid><description>前置知识：动态开点线段树。
二叉树合并 合并是一个递归的过程。首先合并两棵以 $u, v$ 为根的二叉树：
考虑左子树 如果 $u, v$ 都没有左子树，那么直接留空； 如果只有 $u$ 有左子树，那么 $u$ 的左子树保留不动； 如果只有 $v$ 有左子树，那么将 $v$ 的左子树接过来，成为 $u$ 的左子树； 如果 $u, v$ 均有左子树，那么递归合并 $u, v$ 的左子树，结果赋给 $u$ 的左子树。 考虑右子树 如果 $u, v$ 都没有右子树，那么直接留空； 如果只有 $u$ 有右子树，那么 $u$ 的右子树保留不动； 如果只有 $v$ 有右子树，那么将 $v$ 的右子树接过来，成为 $u$ 的右子树； 如果 $u, v$ 均有右子树，那么递归合并 $u, v$ 的右子树，结果赋给 $u$ 的右子树。 最后我们就将两棵二叉树合并成了一个以 $u$ 为根的二叉树。
复杂度分析：在上面的过程中，仅当 $u, v$ 均有左（右）孩子时才会进行递归，访问这个左（右）孩子。时间复杂度就是两棵二叉树中重复的结点的数量。</description></item><item><title>Luogu-P1776 宝物筛选</title><link>https://blog.chungzh.cn/oi-history/luogu-p1776/</link><pubDate>Fri, 12 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/luogu-p1776/</guid><description>Luogu-P1776 宝物筛选
题意 终于，破解了千年的难题。小 FF 找到了王室的宝物室，里面堆满了无数价值连城的宝物。
这下小 FF 可发财了，嘎嘎。但是这里的宝物实在是太多了，小 FF 的采集车似乎装不下那么多宝物。看来小 FF 只能含泪舍弃其中的一部分宝物了。
小 FF 对洞穴里的宝物进行了整理，他发现每样宝物都有一件或者多件。他粗略估算了下每样宝物的价值，之后开始了宝物筛选工作：小 FF 有一个最大载重为 $W$ 的采集车，洞穴里总共有 $n$ 种宝物，每种宝物的价值为 $v_i$，重量为 $w_i$，每种宝物有 $m_i$ 件。小 FF 希望在采集车不超载的前提下，选择一些宝物装进采集车，使得它们的价值和最大。
对于 $100%$ 的数据，$n\leq \sum m_i \leq 10^5$，$0\le W\leq 4\times 10^4$，$1\leq n\le 100$。
解法 1：二进制优化 每一个数都可以表示成 $2$ 的幂的和（因为每一个数都可以用二进制表示）。
时间复杂度：$O(nW\sum \log m_i)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include &amp;lt;bits/stdc++.</description></item><item><title>Luogu-P2254 「NOI2005」瑰丽华尔兹</title><link>https://blog.chungzh.cn/oi-history/luogu-p2254/</link><pubDate>Fri, 12 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/luogu-p2254/</guid><description>「NOI2005」瑰丽华尔兹
题意 不妨认为舞厅是一个 $N$ 行 $M$ 列的矩阵，矩阵中的某些方格上堆放了一些家具，其他的则是空地。钢琴可以在空地上滑动，但不能撞上家具或滑出舞厅，否则会损坏钢琴和家具，引来难缠的船长。每个时刻，钢琴都会随着船体倾斜的方向向相邻的方格滑动一格，相邻的方格可以是向东、向西、向南或向北的。而艾米丽可以选择施魔法或不施魔法：如果不施魔法，则钢琴会滑动；如果施魔法，则钢琴会原地不动。
艾米丽是个天使，她知道每段时间的船体的倾斜情况。她想使钢琴在舞厅里滑行的路程尽量长，这样 1900 会非常高兴，同时也有利于治疗托尼的晕船。但艾米丽还太小，不会算，所以希望你能帮助她。
$100%$ 的数据中，$1\leq N, M \leq 200$，$K \leq 200$，$T\leq 40000$。
分析 首先我们定义一下状态。设 $dp[t][i][j]$ 表示 t 时刻，在 $(i, j)$ 滑行的最长路程长度。状态转移方程是 $dp[t][i][j] = \max(dp[t-1][i][j], dp[t-1][i^{&amp;rsquo;}][j^{&amp;rsquo;}])$，$i^{&amp;rsquo;}$ 和 $j^{&amp;rsquo;}$ 是合法的走过来的位置，取决于 $t$ 时刻船体倾斜的方向。
这样设计状态的话，时间复杂度为 $O(TNM)$，空间似乎也是问题。
这时候我们发现还有一个变量 $K$ 没有用到，考虑把状态设为 $dp[t][i][j]$ 表示在第 $t$ 时间段内，在 $(i, j)$ 滑行的最长路程长度。时间复杂度为 $O(KN^3)$，暂时还不行。
我们看看能不能进一步优化。下面假设当前船体倾斜的方向是东。设当前时间段长度是 $tim$，上一个时间段钢琴的位置在 $(i, m)$，那么 $dp[t][i][j] = \max_{j-m&amp;lt;=tim}{dp[t-1][i][m]+j-m}$。在这个式子中，只有 $m$ 一个变量，并且 $j-m&amp;lt;=tim$，合法决策在一段相邻区间内，可以用到单调队列优化！
对于两个决策 $m_1 &amp;lt; m_2$，$m_2$ 优于 $m_1$ 时仅当：
$$ \begin{aligned} dp[t-1][i][m_1]+(j-m_1) &amp;amp; &amp;lt; dp[t-1][i][m_2]+(j-m_2) \\ dp[t-1][i][m+1]+m_2-m_1 &amp;amp; &amp;lt; dp[t-1][i][m_2] \end{aligned} $$</description></item><item><title>点分治笔记</title><link>https://blog.chungzh.cn/oi-history/centroid-decomposition/</link><pubDate>Tue, 02 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/centroid-decomposition/</guid><description>点分治，外国人称之为 Centroid decomposition，重心分解。
何为树的重心 学习重心分解之前，自然要先了解重心。
下面统一用 $n$ 表示树上结点的个数。
在一棵树中，如果删除一个顶点后得到的最大子树的顶点数最少，那么这个点就是树的重心（Centroid）。
重心的性质：
删除重心后得到的所有子树，其顶点数必然不超过 $n/2$。
证明：选取任意顶点作为起点，每次都沿着边向最大子树的方向移动，最终一定会到达某个顶点，将其删除后得到的所有子树的顶点数都不超过 $n/2$。如果这样的点存在的话，那么也就可以证明删除重心后得到的所有子树的顶点数都不超过 $n/2$。
记当前顶点为 $v$，如果顶点 $v$ 已经满足上述条件则停止。否则，与顶点 $v$ 邻接的某个子树的顶点数必然大于 $n/2$。假设顶点 $v$ 与该子树中的顶点 $w$ 邻接，那么我们就把顶点 $w$ 作为新的顶点 $v$。不断重复这一步骤，必然会在有限步停止。这是因为对于移动中所用的边 $(v, w)$，必有 $v$ 侧的子树的顶点数小于 $n/2$，$w$ 侧的子树的顶点数大于 $n/2$，所以不可能再从 $w$ 移动到 $v$。因而该操作永远不会回到已经经过的顶点，而顶点数又是有限的，所以算法必然在有限步终止。
树中所有顶点到某个顶点的距离和中，到重心的距离和是最小的；如果有两个重心，那么到它们的距离和一样。
把两棵树通过一条边相连得到一棵新的树，那么新的树的重心在连接原来两棵树的重心的路径上。
在一棵树上添加或删除一个叶子，那么它的重心最多只移动一条边的距离。
更多证明请见：树的重心的性质及其证明 - suxxsfe - 博客园 (cnblogs.com)
寻找树的重心 根据重心的定义，先以 $1$ 为根进行 DFS。在递归中计算子树大小 $siz[u]$，并求出最大的子树的大小 $maxs[u]$，比较出重心 $centroid$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void getCentroid(int u, int fa, int s) {  siz[u] = 1;  maxs[u] = 0;  for (int i = head[u]; i !</description></item><item><title>关于 int 与 long long 的运算速度</title><link>https://blog.chungzh.cn/oi-history/int-vs-longlong/</link><pubDate>Fri, 22 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/int-vs-longlong/</guid><description>前言 写一道 CF 题的时候，算法明明是正确的，却一直都 TLE。最后把一个 long long 类型的数组改成了 int，竟然就 AC 了。。
这不禁引发了我的思考，int 与 long long 的运算速度不一样吗？
不严谨测试 由于本菜鸡并没有什么计算机基础原理的知识，只好做了一个测试。当然，这个测试其实很不严谨，没有很大的参考价值。我也就图一乐，哈哈哈哈哈
测试环境 电脑：Lenovo Yoga 14sACH 2021 系统：Windows 11 25163.1010 CPU：AMD Ryzen 7 5800H with Radeon Graphics (16) @ 3.200GHz RAM：16.0 GB 编译器：GCC 11.2.0 代码 仅仅是为了图一乐， 我第一次使用了 Google Benchmark 这一工具。其实挺好上手的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include &amp;lt;benchmark/benchmark.</description></item><item><title>Treap 笔记</title><link>https://blog.chungzh.cn/oi-history/treap/</link><pubDate>Wed, 09 Feb 2022 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/treap/</guid><description>Treap = Tree + Heap
二叉搜索树（BST） 在学习 Treap 之前，需要先了解一下二叉搜索树（BST, Binary Search Tree）：
设 $x$ 是二叉搜索树中的一个结点。如果 $y$ 是 $x$ 左子树中的一个结点，那么 $y.key \lt x.key$。如果 $y$ 是 $x$ 右子树中的一个结点，那么 $y.key \gt x.key$。
BST 上的基本操作所花费的时间与这棵树的高度成正比。对于一个有 $n$ 个结点的二叉搜索树中，这些操作的最优时间复杂度为 $O(\log n)$，最坏为 $O(n)$。随机构造这样一棵二叉搜索树的期望高度为 $O(\log n)$。然而，当这棵树退化成链时，则同样的操作就要花费 $O(n)$ 的最坏运行时间。
由于普通 BST 容易退化，对于它的实现就不再赘述。在实践中需要使用如 Treap 这样的平衡二叉搜索树。
Treap 顾名思义，Treap 是树和堆的结合。它的数据结构既是一个二叉搜索树，又是一个二叉堆。
在 Treap 的每个结点中，除了 $key$ 值，还要保存一个 $fix$（更常见的是 $priority$）值。这个值是随机值，以它为依据来同时建立最大堆（或最小堆）。因为 $fix$ 值是随机的，所以可以让这棵树更加平衡，高度更接近 $O(\log n)$。它的各种操作期望时间复杂度都是 $O(\log n)$。
旋转式 Treap 旋转式 Treap 的常数较小。</description></item><item><title>CSP-J 2021 游记</title><link>https://blog.chungzh.cn/oi-history/csp2021/</link><pubDate>Sat, 23 Oct 2021 18:35:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/csp2021/</guid><description>CSP-J/S 认证注意事项：
&amp;hellip;&amp;hellip;
11. 祝各位选手好运。
CSP-J1 初赛前有点小紧张。
赛前勉强做了几套试卷，然后就上考场了。
刚考完对答案的时候发现 J 组才 72，看洛谷上大家都说今年 J 组简单了 blabla，分数线肯定会升，然而我却觉得好难，那一个星期都害怕极了。。。结果分数出来了才发现洛谷那群人真是扯啊哈哈哈
S 组才 48 分，没有成功压线。（其实就算去了复赛也拿不了分。。
CSP-J2 比赛前一晚上八点在学校出发。逃掉了晚自习（尽管是星期五
去到酒店大概也九点半了，洗完澡，看了会儿凤凰台，然后就睡了。
第二天早上六点半起床，吃完自助餐（和上一年的变化不大，挺好吃的），七点二十出发。
然后进考场。
电脑好像是 Ryzen 3600，8GB 内存。
八点半开考。
密码很乱，6ewid\n16384#，监考员一开始还直接忽略最后面那个井号了。。。
打开题目，发现第一第二题题面好长，有点慌了起来&amp;hellip;
T2 尤为毒瘤，到了 9:22 才搞完了。。。感觉挺很危险的，好怕翻车。
10:08，肝完 T3，77 行代码，写完人都瘫了。。感谢第三个样例，一个一个找情况。。。
（不知道怎么比对两个文件的内容，于是直接打开 Sublime Text 开始用查找来找不同。。。
赶紧吃了根士力架，然后去上了个厕所。
10:55，T4 过样例了，打得比 T3 轻松多了，当然也不可能拿满分。。。其实也不知道该怎么做，直接乱搞，做法非常诡异。。看看效果怎样吧，能骗到 50 分就是胜利。
还有一点，今年 NOI Linux 2 还行。（毕竟上一年给了虚拟机但是系统有密码，根本打不开，笑死）在里面编译了几次代码，虽然也没什么用。运行起来还蛮快的。
总的来说，这次考得还行。题目有点诡异，没有 dp，没有搜索，个人感觉侧重考基本功。
等成绩吧。
UPD 1 (2021/10/23 22:50)：
广东源代码出了。
candy: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include &amp;lt;bits/stdc++.</description></item><item><title>线段树笔记</title><link>https://blog.chungzh.cn/oi-history/segment-tree/</link><pubDate>Wed, 04 Aug 2021 00:00:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/segment-tree/</guid><description>线段树是一种高端的数据结构，可以用来在区间上进行信息统计。它能够在 $O(logN)$ 的时间复杂度内实现单点/区间修改、区间找最大值/最小值/总和/&amp;hellip;，适用于大规模的区间统计。
如下图就是一棵线段树。在结点中，你可以存对应区间的最大值，最小值，总和等等。
对于每一个结点 $i$，它的两个子结点分别是 $2i$ 和 $2i+1$。因此，在开树的数组时，最好要开到 $4N$ 的大小。
关于 $4N$，详见 OI-Wiki。
建树 下面是一个求区间和的线段树的建树代码。
通过 DFS 建树，到叶结点，然后一路回溯求出和。
1 2 3 4 5 6 7 8 9 10 11 12 13 void build_tree(int cur, int left, int right) { // cur 为当前树的根，[left, right] 是当前树对应的区间 if (left == right) { // 到叶子节点了，区间长度为 1，总和就是它本身 tree[cur] = a[left]; return ; } int leftSon = cur*2, rightSon = leftSon+1; int mid = (left+right)/2; build_tree(leftSon, left, mid); build_tree(rightSon, mid+1, right); tree[cur] = tree[leftSon]+tree[rightSon]; // 求和 } 这里求 leftSon，rightSon，mid 的模式在线段树的所有操作中都会用到。</description></item><item><title>CSP-J 2020 游记</title><link>https://blog.chungzh.cn/oi-history/csp-j-2020/</link><pubDate>Sat, 14 Nov 2020 17:15:00 +0000</pubDate><guid>https://blog.chungzh.cn/oi-history/csp-j-2020/</guid><description>他山之石，可以攻玉。
CSP-J1 入学你校几乎两个月都在搞初赛。
结果初一还是只有两个人过了
最后是 73.5 分，水过去了。
CSP-J2 Day 0 要去大学城的广大附中，就去住酒店了。
在 tjl 大佬房间里 玩，其实是在看凤凰台。
依稀记得那晚上拜登和特朗普的比分是 264 : 214，林郑去北京见韩正了。
CCTV-7 上面中科院在帮农民种橘子？
十点多就回去昏昏沉沉地睡了，还挺香（
Day 1 早上六点半就醒了。
在酒店吃了顿自助早餐，真香。
到处都是石实的大佬 %%% 。
然后搭着同校热心家长的车前往广大附中。
门口还挺热闹的，好像发生了许多事情：
黄老师身份证不见了，其实藏在袋子里 某大佬没带准考证 还有没带粤康码的 于是感到很庆幸，没入考场的时候也是一场考验。。所以说带齐资料很重要。
很快就进考场了。
电脑有 8G 内存，装的是 Windows 10 神州网信政府版，感觉只是开始菜单看上去稍有不同。
下发题目之后几分钟我还在打 A+B，打完之后才一愣一愣地抄密码解压题目。
翻了一下，发现第一题好难，于是从第二题开始做。
T2 刚开始竟然用了 sort，到了最后试大样例的时候才看到一卡一卡的，于是又改成了插入排序，以为没问题了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include &amp;lt;iostream&amp;gt;#include &amp;lt;cstdio&amp;gt;#include &amp;lt;fstream&amp;gt;#include &amp;lt;algorithm&amp;gt;using namespace std; bool cmp(int a, int b) { return a &amp;gt; b; } int a[100005]; int main() { int n, w; scanf(&amp;#34;%d%d&amp;#34;, &amp;amp;n, &amp;amp;w); for (int i = 0; i &amp;lt; n; i++) { int inp; scanf(&amp;#34;%d&amp;#34;, &amp;amp;inp); int j = 0; for (; j &amp;lt; i; j++) { if (a[j] &amp;lt; inp) break; } for (int k = i; k &amp;gt;= j; k--) { a[k+1] = a[k]; } a[j] = inp; int planNum = max(1, (int)((i+1)*0.</description></item></channel></rss>