刷题杂记 Pt.1
-
P3567 [POI2014] KUR-Couriers、P4559 [JSOI2018] 列队、P9695 [GDCPC2023] Traveling in Cells
注意在这里补一段关于线段树二分的分析。
-
-
主席树做法:以出现位置为关键值。
-
莫队做法:需要用到回滚莫队。回滚莫队被用于处理信息只能加不能减或只能减不能加的情况。以只加不减举例,其实这个的思想并不难,就是每次做莫队的时候,可以发现 \(r\) 是单增的,只有 \(l\) 是不规律的,每个询问就从 \(l\) 所在块的右端点扫到 \(l\) 即可。另外一道例题是 #2874. 「JOISC 2014 Day1」历史研究。
-
-
-
线段树维护的基本条件为信息具有“可合并性”,因此还可以维护一些奇奇怪怪的东西,如本题这个值域对应的路径。
-
在有很多很多 LCA 询问时,有如下做法:预处理欧拉序,套个 ST 表,能做到 \(O(n \log n)\) 预处理 \(O(1)\) 查询 LCA。注意这里欧拉序的做法是在进入该点时记录,从儿子出来时再记录。
-
程序复杂度一定要注意考虑常数因子!
-
线段树维护奇怪信息常用到结构体。都要有个 Merge 函数,而且都要有个单位元。个别题目也有那种不用单位元的写法,但事实证明设置单位元是最普适的。P2486 [SDOI2011] 染色
-
2024.6.8 测试
-
T1:阶乘某些信息的统计可做前缀和。
-
T2
感觉这道题考场上浪费了好多时间。。。
关键:一定要想清楚 DP 状态的含义。
还有这道题能够通过剔除无用状态进行 DP 优化。
-
T3
-
本质是在计算时,使用捆绑统计 + 增量法,刚好用 DP 统计。
-
没有想到在 DP 方程本身中使用容斥是我的问题
-
当有一段已经是排列了,你想在其基础上进行延伸,那么应当在颜色数中也进行扣除。(这个属于是对排列组合的掌握不够熟练。。。)
-
-
T4:动态开点线段树可用于给信息分类。
-
-
感觉好多这种无查询的数据结构题都是先挑选一个枚举点,然后再考虑怎么用数据结构维护这个枚举点的信息。(这种方法实际就是扫描线。)
-
拆式子,分解可维护信息。
-
通过加点操作维护只加不减的信息。
-
-
这是一种线段树的 Trick——“单侧递归线段树”。它可以在 \(O(n \log^2 n)\) 的时间复杂度内,以线段树二分代替可持久化平衡树的功能。
关键的步骤就是在每次
Push_up
的时候做线段树二分,合并得到新区间。点击查看代码
inline int Update(int pt, double k){ if(l(pt) == r(pt)) return mx(pt) > k ? 1 : 0; if(mx(pt*2) <= k) return Update(pt*2+1, k); else return Update(pt*2, k)+cnt(pt)-cnt(pt*2); } inline void Push_up(int pt){ mx(pt) = max(mx(pt*2), mx(pt*2+1)); if(mx(pt) < eps) return; cnt(pt) = cnt(pt*2)+Update(pt*2+1, mx(pt*2)); return; }
学习 flowerletter 的网课时,他给出了能够使用单侧递归线段树的两个条件:
-
原平衡树中元素,都是线段树中的叶节点。
-
可以维护“无关”信息。(即,与每一个平衡树节点具体是什么没有关系,如本题我们只需要知道它们中的最大值即可。)
譬如该题楼房重建维护的就是一个上升序列的长度。
有一个相似的问题是动态插入点,维护下凸壳。在这种情况下并不能使用单侧递归线段树,因为判断是否在凸壳上的方法需要用到叉乘,而这是不满足上述条件的。
单侧递归线段树给我们的启示是:线段树的
Push_up
不一定是 \(O(1)\) 的,也可以跟本题一样是些别的。 -
-
线段树合并什么时候用?
flowerletter:关键在“线段树”,不在“合并”。
例如 P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并,其实可以使用启发式合并哈希表。
-
比较典的线段树合并题目。需要注意:
-
搭配链表,维护序列末尾的数。
-
ll
! -
以值域建线段树。
-
-
-
看到异或相关式子,想到将二进制位拆开维护,或者想到 01-trie。01-trie 可以在支持合并、整体加 1、整体减 1 的情况下支持维护它们的异或和。
-
普通 01-trie 可看作长度为 \(2^k\) 的值域线段树。(当然这道题这么说其实不太合适,因为这道题是低位在上的 01-trie。)
-
2024.6.12 测试
-
题面大概是:给定一个矩形,其中每个格子被染上了不同的颜色;再给定一个参数 \(k\),试对于矩形每个格子求出其作为左上角时,内部颜色数不超过 \(k\) 的正方形的最大边长。
-
降维思考。先思考该题在一维上的做法,再类推至二维。
-
尽量利用之前的信息。这种非逐个询问分别回答,而让你求出 \(1 \sim n\) 各自是什么的,多半都要利用到该条思想。与此同时,确定一个良好的枚举顺序也就很重要。
-
单调栈/队列类型均摊复杂度。
-
-
题面:多次询问,给定等差数列首项、公差、项数,问数列乘积。(在模意义下进行 。)
-
小模数,必有猫腻。这个模数只有 \(10^6\),肯定是用来预处理东西的。果然观察可以发现,项数超过模数说明乘积必然为 \(0\)。
-
部分分有助于整体的思考。比如此题给出的 \(d=1\) 这种情况,就是阶乘相关的,再联系上前面的预处理(肯定只能预处理阶乘啊),暗示我们正解须将给定转化为阶乘。
-
模意义下的除法无所谓除不除尽,用逆元就可以了。其实最后这道题我就卡在这里了,没有想到用逆元乘上首项进行转化!
-
-
这道题的题面真的写得超级拉跨。。。完全叫人读不懂。。。
简化版题面:给定一个长度为 \(n\) 的数组 \(s\),对于位置 \(i \in [2, n-1]\),令它的权值为 \(k = s[i]+i-1\);再随机生成一个长度为 \(n-i\) 的 \(\texttt{01}\) 串,第 \(n-i\) 个位置以 \(100\%\) 的概率为 \(\texttt{1}\),除此之外的位置分别以 \(50\%\) 的概率为 \(\texttt{0}\) 或 \(\texttt{1}\)。问随机在 \([2, n-1]\) 选取 \(i\) 的情况下,所生成的 \(\texttt{01}\) 串中连续的 \(\texttt{0}\) 段的长度全部不超过 \(k\) 的概率有多大?
首先枚举 \(i\)。正难则反,考虑使用容斥。
记得前几天考容斥,同学评讲的时候,列了这么一个式子:
\[ans = \sum_{S \subset T} (-1)^{|S|} f_S \]这个是容斥的基本式子,所有容斥都是建立在该基础上发生的。然而实则很少直接使用这个式子,因为它显然是 \(2^n\) 的一个东西。一般来说,我们都会对一些状态进行“捆绑”。
比如说,前几天考的那一场中,我们就是这么捆绑的:
\[ans = f_{\emptyset} - \sum_{i=1}^n \sum_{S\subset\{1,2,\dots,i-1\}} f_{S\cup\{i\}} \]之所以这里没有容斥系数的正负变化,是因为此处的每个捆绑状态之间是两两互斥的。而且 \(\sum_{S\subset\{1,2,\dots,i-1\}} f_{S\cup\{i\}}\) 是一个可以通过容斥 DP 的东西。
说回今天的这个。如果考虑使用和上面一样的捆绑方式,即枚举从哪个点开始连续段,复杂度是过不去的。本质上是因为连续段有很多重复部分。
然后题解就想到了一个非常精妙的捆绑方案——设 \(f_j\) 表示 钦定 了 \(j\) 段长度不合法的连续段时,总方案数为多少。【注意:此处不合法连续段的定义为“一段长度为 \(k+1\) 的 \(\texttt{0}\) 和一个末尾处的一个 \(\texttt{1}\)”。】
这里需要解释一下“钦定”到底是个什么鬼。其实就是必选若干个,然后在剩下的地方任选,此时的方案数。如果用式子表示的话,如下:
\[f_i = \sum_{S \subset T} [|S| = i] \sum_{C \subset T} [S \subset C] \]\[ans = \sum_{i=0}^n (-1)^i f_i \]可以发现,钦定实际可能会在同一个捆绑状态内形成重复,而一般容斥仅在不同的捆绑状态之间形成重复,这便是使用钦定捆绑的容斥与一般容斥的区别,这也一定程度上致使钦定容斥比较反直觉,但仔细思考一下其实和一般容斥是一样的。
其实这里还有两种理解方式:
-
递推法(容斥 DP 法)。设 \(g_j\) 表示恰好选 \(j\) 个时的方案数,则有:
\[g_j = f_j - \sum_{i=j+1}^n \dbinom{i}{j} g_i \]当状态转移更复杂一点(如前几天那道题),用递推法会更容易一些。
-
二项式反演
不晓得的可以在这里看一眼 二项式反演。
观察容易发现有:
\[f_k = \sum_{i=k}^n \dbinom{i}{k} g_i \]故此可使用二项式反演,有:
\[g_0 = \sum_{i=0}^n (-1)^i \dbinom{i}{0} f_i = \sum_{i=0}^n (-1)^i f_i \]二项式反演的最大用途就是解决“钦定”和“恰好”之间的关系。
说回本题思路本身。\(f_j\) 可以使用插板法轻松解出,算概率时注意一下最后一个位置需要特判是否参与插板。至于最后的复杂度,由于有 \(k_i \ge i-1\),故复杂度为调和级数 \(O(n \log n)\)。
-
-
涉及到区间推平操作,故此我写了一个颜色段均摊。这个东西就是用
set
维护每个颜色段,特判一些东西就行了。具体的细节我打算写在过会儿一道题里面。
-
这道题的区间范围部分可以用到颜色段均摊维护。具体就是在
set
里面存一个区间就行了。但细节有点多,如下:-
insert
的返回值为一个pair
,其中它的first
恰为插入位置的iterator
。 -
分裂操作记得判空区间。
-
推平操作设计到对左右端点的两次分裂。我们需要左端点所在区间的迭代器、右端点所在区间后一个区间的迭代器(因为左闭右开对 stl 比较方便处理)。
-
在两次分裂时,注意前一次分裂得到的区间可能在后一次分裂时再次分裂,故不能在第一次分裂时就保留区间迭代器。
还有就是线段树分裂方面的问题。我发现我对于动态开点线段树递归的终止条件一直不是很拿手。所以在这里来浅浅总结一下。
-
整个区间都合法。
-
叶节点。(尤其是存在不合法的叶节点的时候。)
以后不管怎样,我都不要想着偷懒,必须把这两个终止条件都写上去。
-
-
-
类吉司机线段树——基于值域的势能分析。
-
多标记线段树一定注意标记之间的关系。比如本题,既有区间推平又有区间加,将二者设置为不能共存是最简单的。
-
-
-
差分约束的某些限制表征出来一定是负边权,不管怎样取反都是如此。如果复杂度不接受 \(O(nm)\),那必须更换表达方式。
-
以一正一负的边权设置,将数量相同转化为和为 0。
-
差分约束(在跑最短路的前提下)实际求的是 dis 字典序最大 的一组解,因为我们实际求的是答案的上界。
-
-
论我人生的第一道蓝题降绿了,我人生的第一道紫题降蓝了,我人生的第一道黑题估计马上也要降紫了这件事边带权并查集:维护点到根路径上的信息。这里的信息可以表达某种有向传递性。比如本题对 \(3\) 取模,表达了吃与被吃与同类的关系。实现上最好写不带返回值的 Find。
-
-
建立 DFS 树后,无向图上的边只分为树边、返祖边两种(不含横叉边)。
-
切忌搞混割边求法与割点求法。(割边不取等。)
-
-
-
看到若干数求最大异或,一定想到线性基。(这属于给整道题定开头方向。)
-
记住,DFS 树是个思考图论问题的利器。本题中,DFS 树上找出的环的异或值,可以表出所有环的异或值。(DFS 树上找出的环都只有一条非树边,别的环都有至少两个非树边,故都可以通过找出环表达。)
-
异或的消去律往往有一些反直觉的贪心,要敢想敢猜。
-
2024.6.18 测试
-
题意:给定若干自然数,在其中选出一些数并将选出的数划分为若干集合,需要最大化每个集合 \(\text{mex}\) 的异或和。
显然每个集合的最优选法就是 \(\{0,1,\dots,x-1\}\)。这里有一个重要的贪心结论:如果想要异或得到一个数,那么应当用尽量少的数来表出它,于是就应当尽量选进行与运算结果为 0 的数。
我当时想当然地类比了线性基,只要使得答案更大的数我就加进来,要不是数据比较水,分都挂没了。对于 T1 的贪心结论,不要慌,要小心验证!
-
我认为考试时做的不是很好的地方:
-
我当时读题读错了。当想了半天都还没思路时,看一遍题面。
-
没有发现距离之和可等价于“白点深度之和 - 黑点深度之和”的形式化表述。想问题时要总结形式化表述!
我当时写的解法是左偏树,但正解是一个非常妙的并查集。大概是从上到下,对每个黑点维护它上方深度最大的白点,将每个黑点/选过的白点合并到它最近的没选过的白点所在的集合中去。这里对并查集的应用让我想到用 Tarjan 求 LCA,里面的并查集维护的就是 LCA。
所以总结一下在什么时候能够使用这种并查集方法呢?大概是在树上从上往下递归,维护最近的某类祖先的时候吧。
-
-
dottle 在评讲时说,看见题目让你构造的图边数巨大无比,且图边有特殊性质时,可以尝试以下两条路径:
-
优化建图,一般会采用建虚点的策略;
-
尝试非最短路算法。
本题中就可以把每个二进制位拆出来建虚点。然后因为本题还要在线询问最短路,就想到用 Floyd 预处理每个二进制位之间的最短路。复杂度 \(O(n \log^2 n)\)。
但还可以更优。题解中的 \(O(n \log n)\) 做法:
-
优化 Floyd 建边过程
可以发现两个位 \(i,j\) 之间的边权实际为同时包含二者的最小数乘 2。通过做“高维后缀和”,便可以求得,时间复杂度优化到 \(O(n \log n)\)。具体地,我们对超集做高维前缀和,得到的结果即为高维后缀和。
-
优化答案统计
实际上,我们还可以通过再做一次高维前缀和,得到每个二进制位到每个“二进制位的集合”的最短路。看似这里对 \(\log n\) 个二进制位都做高维前缀和,有 \(O(n \log^2 n)\) 的复杂度。但实际上由于这里的信息只在 2 的整次幂中,每次只需要将新的 \(2^k\) 的贡献加入就可以了。具体地,对于一个集合,即一个整数 \(x\),有:\(f[x] = \min(f[x \text{ xor } \text{lowbit}(x)], f[\text{lowbit}(x)])\)。答案统计就遍历一遍起点的二进制位即可。时间复杂度 \(O(n \log n)\)。
【注】一般高维前缀和的写法为:依次对每一位做前缀和,如此可保证不重不漏。但由于本题求的是最值的前缀和,并不需要保证“不重”,因此可以先枚举数再枚举位。
-
-
其实是一道蛮 trival 的 DP 优化……(如果不算那个神奇的 \(O(n^2 \log n)\) 做法的话……)
-
先给出部分分 DP 架构再想优化。
-
复杂度分析:看上去是带 \(\log\) 的,但如果把复杂度和式一五一十写出来会发现其实是不带的。
-
可以将转移方程中无关的两部分分开枚举,起到优化复杂度的效果。
然后就是很神仙的 \(O(n^2 \log n)\) 做法:设 \(f[i][j]\) 表示当前已经走了 \(i\) 个节点、位于节点 \(j\) 时,之前走过哪些点是确定的。那么只需要枚举下一步可能走到哪些点即可。
-
-
-
进行 Kruskal 算法时,对于边权相同且选择互斥的边,选择哪一个都不影响后续选择。
-
缩点建图,找出割边,以判断哪些必选。
-
2024.6.20 测试
炸裂计数场。
-
作除法去因子,将在 \([l, r]\) 内找出必须带因子 \(x\) 的数变为在 \([\lceil\frac{l}{x}\rceil, \lfloor\frac{r}{x}\rfloor]\) 中找出任意数。
-
这道题思路的起点是枚举出所有可能的 \(\gcd\) 与 \(\text{lcm}\),因为 \(\gcd|\text{lcm}\) 所以 \(\gcd(\frac{\text{lcm}}{\gcd}+1) = m\),可以通过 \(O(\sqrt{m})\) 的枚举得到。
接下来考虑将集合中所有的数除以 \(\gcd\),就变为求最小公倍数为 \(t = \frac{\text{lcm}}{\gcd}\)、且整体互素的方案数。【考场上我并没有想到做这一步变换,而是直接靠着 \(\gcd, \text{lcm}\) 的概念在推,虽然本质和这个是一样的,但这也是我想不到 std 的做法的原因之一。】
很容易求出的是 \(\text{lcm}|t, \gcd|t\) 的方案数。具体地,设 \(d(x)\) 表示 \(x\) 的因数个数,方案数就是 \(\binom{d(t)}{n}\)。我们希望通过做容斥,将上面 \(\gcd, \text{lcm}\) 的限制都解决。
再往下走,就有两种方法了:
-
通过 \(\text{lcm}\) 的因子做容斥
设 \(f(x)\) 表示 \(\text{lcm}|x\) 且 \(\gcd|x\) 的方案数,\(g(x)\) 表示 \(\text{lcm}=x\) 且 \(\gcd|x\) 的方案数,容易发现有 \(f(x) = \sum_{d|x} g(d)\)。
看着很眼熟吧?这是一个狄利克雷前缀和。惯常的套路是利用莫比乌斯反演 \(g(x) = \sum_{d|x}\mu(d)f(\frac{x}{d})\) 推导 \(g(x)\),但这个方法由于 \(f,g\) 非积性函数没法用筛法优化复杂度,反倒增加了求 \(\mu\) 的麻烦。于是可以给出莫比乌斯反演的一种容斥递推形式:
\[g(x) = f(x) - \sum_{d|x} g(d) \]还需要解决 \(\gcd=1\) 的限制。再设 \(h(x)\) 表示 \(\text{lcm}=x\) 且 \(\gcd=1\) 的方案数,容易发现有 \(g(x) = \sum_{d|x} h(d)\)(这一步可以看作将所有数除以 \(\frac{x}{d}\) 后,\(\gcd\) 就变为了 \(1\))。\(f,g\) 和 \(g,h\) 的关系式竟然奇迹般地是一样的,真的非常反直觉!然后再做一遍一样的容斥就可以了。
单次容斥时间复杂度 \(O(d(t)^2)\),\(d(t)\) 的理论上界为 \(\sqrt{m}\) 但显然卡不满。(事实上,对于 \(x \le 10^9\) 有 \(d(x) \le 90\),可以根据 这里 的公式进行推导。)
这种方法是 std 的做法,很巧妙,代码也很简洁。我认为关键就在看出这个“狄利克雷前缀和”。
-
通过 \(\text{lcm}\) 的质因数分解做容斥
设 \(t = \prod_{i=1}^k p_i^{c_i}\),则有所有因数集合 \(T = \{x | x = \prod_{i=1}^k p_i^j, 0 \le j \le c_i\}\)。那么限制可转化为:在 \(T\) 中选出 \(n\) 个数时,对于每一个质数 \(p_i\),我们必须既选到 \(p_i^0\) 又选到 \(p_i^{c_i}\)。
设 \(f(S)\)(\(S \subset \{1,2,\dots,k\}\))表示对于任意 \(i \in S\),都绝对满足没有选到 \(p_i^0\) 或没有选到 \(p_i^{c_i}\)。根据容斥定义,易列出以下式子:
\[ans = \sum_{S \subset \{1,2,\dots,k\}} (-1)^{|S|} f(S) \]注意到其实我们根本不需要用任何容斥捆绑来优化上面的式子,DFS \(O(2^k)\) 暴搜即可,因为它实际上与 \(d(t)\) 是同阶的(\(d(t) = \prod_{i=1}^k (c_i+1)\),而 \(c_i+1 \ge 2\),所以 \(d(t) \ge 2^k\))。
那么现在唯一的问题就是如何求出 \(f(S)\)。\(f(S)\) 依旧不好直接求,考虑再做一次容斥。
为了解释这个容斥,我们先来研究一些简单一点的问题。【请注意:下面出现的函数 \(g, h\) 与 \(f\) 没有直接的加减关系!只是用来帮助理解的!】
如果我们对“不合法”的定义仅限于没选到 \(p_i^{c_i}\),而不管有没有选 \(p_i^0\),设此时的方案数为 \(g(S)\),怎么求 \(g(S)\) 呢?此时我们可以列出如下的式子:
\[g(S) = \dbinom{\prod_{i=1}^k (c_i + 1 - [i \in S])}{n} \]如果只关心 \(p_i^0\) 而不管 \(p_i^{c_i}\) 是同理的,方案数都为 \(g(S)\)。
但明显只各自关心 \(p_i^0\) 与 \(p_i^{c_i}\) 会造成重复。再设 \(h(S)\) 表示 \(S\) 内每一个位置都“既没有选到 \(p_i^0\) 也没有选到 \(p_i^{c_i}\)”的方案数,则有:
\[h(S) = \dbinom{\prod_{i=1}^k (c_i + 1 - 2*[i \in S])}{n} \]把所有的式子综合起来——
\[f(S) = \sum_{U \cup V \cup W = S, U \cap V = U \cap W = W \cap V = \emptyset} (-1)^{|W|} \dbinom{\prod_{i=1}^k \begin{cases} (c_i + 1) & i \not \in S \\ (c_i + 1 - [i \in S]) & i \in U \cup V \\ (c_i + 1 - 2*[i \in S]) & i \in W \end{cases}}{n} \]实现上,我们可以用一个 \(O(3^k)\) 的 DFS 计算出 \(ans\)。具体地,每次枚举 \(i \not\in S, i \in U \cup V, i \in W\),注意一下 \(i \in U \cup V\) 时需要维护它对应着几种 \(U, V\) 的不同选法。
发现这时的时间复杂度不与 \(d(t)\) 同阶了,但是为 \(O(3^k) = O((2^k)^{\log_2 3}) \le O(d(t)^{\log_2 3})\),理论上比上面 \(O(d(t)^2)\) 的复杂度还要小一点(。
总结:
-
一般来说,整个序列中含有一个公因数时,我们会将整个序列同时除去该公因数,以简化问题。
-
莫比乌斯反演的容斥递推形式。
-
当容斥没有思路时,从容斥基本式出发。
-
多层容斥,分层分析。
-
-
或许可以称为“动态高维前缀和”的模板?
发现两种情况:“\(O(2^m)\) 修改 \(O(1)\) 查询”“\(O(1)\) 修改 \(O(2^m)\) 查询”,考虑根号平衡,每次修改时修改前 \(\frac{m}{2}\) 位,每次查询时查询后 \(\frac{m}{2}\) 位,总时间复杂度 \(O(n2^{\frac{m}{2}})\)。
-
T4:玄学,先不写了。
2024.6.17 测试
-
也不知道总结个啥(
-
看到这道题,就想到 Floyd;因为是 T1,就往 Floyd 上面套呗。
-
记得判无解。做完以后要 检查 要求是否都完成了。
-
-
真的不知道是怎么想到这个“卷积”的……
卷积本质上求的是“前缀中交错匹配”的信息。这道题通过翻转,就将其变成了“前缀中一一匹配”的信息。
-
-
余数分类。
-
线段树优化 DP,维护最优决策集合。
-
写的时候谨慎,不要引入无用操作。
-
-
-
这道题有着多维的限制(一维字典序 \(rk\),一维字符串下标),可以用离线算法/高维树(主席树、二维线段树)维护。
-
考场上头晕,没想清楚哪些地方取 max 哪些地方取 min。事实证明,还是应该把数据结构所维护的式子写出来,再打代码。
-
吉司机线段树的
update
函数中,切忌在if(se(pt) < v)
这里取等。因为取等后,mx
所包含的数实际是改变了的,这可能会造成许多问题。比如本题中mx
的最小编号会改变;比如在吉司机模板中,mx
的个数会改变。
-
-
思路上,这道题通过将每个数拆为 \(\le j\) 的 \(k\) 个
bool
变量,转化为了 2-SAT 问题。(反正这种给定约束求特解/判断有无解的,不是 2-SAT 就是差分约束。)恶心的在实现上:
-
以后干脆非网络流题目,我都用
vector
存图了,避免计算错误。 -
在 2-SAT 中,不要通过什么标记来判断无解,要用建模本身表达无解。比如可以建立一个边界点,它有一条出边指向自己的对应点。
-
2024.6.22 测试
-
这是一个在模意义下求 lcm 的 trick:先质因数分解,再对每个质数做快速幂。
-
暴搜 DFS 题。
注意即使是 DFS 也要状态压缩,因为我们要做记忆化。
-
线段树维护多位数题。
充分发扬“什么不确定就存什么”的思想:当我们无法确定某特定位数的个数,就把每个位数的个数存下来。
唯一的失误就是当时弱智了,明显这里直接存 \(10^k \bmod p\) 是更简单的做法,我却搞了个把 \(k\) 拆成循环节的复杂做法……
这种错误我也不太清楚如何改正……只能说下次想到做法开始写代码前,反思一下是否有更直接的做法。
-
一道实现比较复杂的矩阵乘法优化 DP,所以没有写。但它的状态设计很有意思:设 \(f[i][0,1][0,1]\) 表示半长为 \(i\) 的回文串,末位是否需要进位,高位是否存在进位。
-
和 Too Many Constraints 差不多,以 \(\le, >\) 划分值域的 2-SAT。
-
看来之前并没有吃透这道题。
本题为“增量法”求二分图最大匹配的经典题目。有几种做法,我们依次分析一下。
设 \(n\) 为答案上界,\(m\) 为边数。
-
枚举/二分 + 匈牙利算法。此时有时间复杂度 \(O(nm \log n)\) 或 \(O(n^2m)\)。
-
二分 + Dinic 算法。此时有复杂度 \(O(m \sqrt{n} \log n)\)。
-
增量法 + EK 算法。由于每次只增加一个点,最大匹配数即最大流最多增加 1,故每次增量后最多找到一条新的增广路,所以每次增量最多进行一次增广,复杂度为 \(O(nm)\)。这种方法比较起前面的“二分 + Dinic”更加普适,不一定得在满足答案单调性的情况下使用。
另外,我针对“最优化算法有哪些”进行了如下总结:
-
枚举,增量法
-
贪心
-
二分答案做判定
-
DP
-
图论建模(最短路、最小生成树与最小树形图、二分图、网络流)
-
-
直接多源多汇面临的两个棘手的问题:
-
一条边既可能被正向通过两次,也可能被反向通过两次,很难建边。(Obviously,它仅可能在 \(a, b\) 之间发生,不可能在 \(a, b\) 以内独自发生。)
-
\(a_1\) 的流一部分错流至 \(b_2\),\(b_1\) 的流一部分错流至 \(a_2\)。
这道题有一个奇妙的结论:有解,当且仅当 \(a_1 \rightarrow a_2, b_1 \rightarrow b_2\) 有解,且 \(a_1 \rightarrow a_2, b_2 \rightarrow b_1\) 有解。
感性证明:
-
对于问题一,它会使得原本反向的边变为同向。
-
对于问题二,在第二次跑最大流时我们绝对可以构造出一组流,使得第一次网络流中的 \(flow(a_1 \rightarrow a_2) + flow(b_1 \rightarrow b_2)\) 等于第二次网络流中的 \(flow(a_1 \rightarrow a_2) + flow(b_1 \rightarrow b_2)\)。故此,有 \(flow(a_1 \rightarrow b_2) + flow(b_1 \rightarrow a_2) = flow(a_1 \rightarrow b_1) + flow(a_2 \rightarrow b_2)\),也就意味着原本错流的部分可以转化为非错流部分。
-
-
我一开始看到这道题还以为是 P6062 [USACO05JAN] Muddy Fields G,但这里的砖块不能重复。
题解中是这么思考的:将每个砖块都表达出来是很困难的,想到通过总点数减去在同一块内部的点数。那么就最大化在同一块内部的点数即可。这个就可以建立二分图做二分图最大独立集。
或许这最关键的一步——“通过总点数减去在同一块内部的点数 ”——可以称作“容斥思想”,正难则反。
-
-
最小割常常被用来表达“若干类收益中必须选择一类”。
-
如果某些最大化问题难以用最大流、费用流之类表达,转化为最小化问题,尝试用最小割。此时最小割表达“若干类收益中必须放弃一类”。
-
\(\inf\) 边防割断。
-
-
-
这道题非常形象地体现了“最小割常常被用来表达若干类收益中必须选择一类”。通过拆解绝对值,我们能够做到。
-
简单分析一下,我们可以得知此题每条链最多割一条边。当实在拿不准每条链是否最多割一条边时:给链上所有边权加上一个大数 \(m\),满足 \(\sum w_i < m, \sum m < \inf\)。
-
2024.6.27 测试
其实这场的问题主要在“先入为主”地给每道题套上这应该是什么算法。比如,我一开始将 T2 误判为倍增/矩阵,将 T3 误判为同余最短路,将 T4 误判为网络流。
-
一个很 naive 的 DP。
-
“删除一个点再求答案”的几种思路:
-
倍增
-
预处理前后缀信息,再拼凑(比如有些矩阵题就可以这么搞)
但显然这个题不用矩阵,用一个桶从后往前扫就可以解决了(不要手残加个线段树,一定要在有信息需要我们做“合并”的时候再出动线段树)。
-
-
这是全场可做题中最值得记录的一道。
这道题的切入点是:
-
模数 \(2\),肯定有很多排列求出来后,对应方案数都是 \(0\)。我们肯定是要去找为 \(0\) 的性质。
-
模数 \(2\),就想着将答案用二项式系数形式表示出来,方便使用 Lucas 定理帮助我们找性质。(考场上把这茬忘了。。。)
-
由于 \(n\) 很大而 \(k\) 比较小,于是可以想到:比起确定每个人选了哪些,确定每个物品被选了几次是更方便的。恰好,如果我们设每个物品被选的次数为 \(b_i\),有总方案数为:
\[n!\prod_{i=1}^k\dfrac{1}{b_i!} = \dbinom{n}{b_1, b_2, \dots, b_k} = \dbinom{n}{b_1}\dbinom{n-b_1}{b_2}\dbinom{n-b_1-b_2}{b_3}\cdots\dbinom{b_k}{b_k} \]这是一个多重集的排列数,或者说,多重组合数。
-
那么 \(\binom{n}{m} \bmod 2\) 在什么情形下为 \(0\) 呢?通过 Lucas 定理展开:
\[\dbinom{n}{m} \equiv \dbinom{n/2}{m/2} \times \dbinom{n \bmod 2}{m \bmod 2} \pmod{2} \]当存在一个 \(i\) 满足 \(n\) 第 \(i\) 位为 \(0\),而 \(m\) 为 \(1\) 时,则为 \(0\)。换言之,当 \(n \& m = m\) 时不为 \(0\)。
有初步结论了。但还是不够,因为不能对每一种选法依次计算与运算。试着找到更简单的结论。
可以发现多重组合数是可以交换顺序的,即:
\[\dbinom{n}{b_1}\dbinom{n-b_1}{b_2}\dbinom{n-b_1-b_2}{b_3}\cdots = \dbinom{n}{b_x}\dbinom{n-b_x}{b_y}\dbinom{n-b_x-b_y}{b_z}\cdots \]因此我们可以把任意数当作第一个。不难想到先用确定的 \(n\) 写出一个限制:对于任意 \(i\),有 \(n \& b_i = b_i\)。
试着能不能通过 \(\binom{n}{b_i} \not \equiv 0\) 推得 \(\binom{n-b_i}{b_j}\) 的限制。因为 \(n \& b_i = b_i\),所以 \(n-b_i\) 只会将 \(n\) 的一部分原为 \(1\) 的二进制位变为 \(0\);而又由于 \(n \& b_j = b_j\) 也被我们限制成立了,所以此时不成立当且仅当 \(b_i \& b_j \ne 0\)。故第二条性质为:对于任意 \(i, j\),有 \(b_i \& b_j = 0\)。
因为有这两条限制,容易想到对 \(n\) 做二进制拆解。问题转化为:对于 \(n\) 为 \(1\) 的每一个二进制位 \(i\),在 \(1 \sim k\) 中任选一个 \(j\) 令 \(2^ia_j\) 为其权值,求权值和为 \(m\) 的方案数。
考虑从低位到高位做 DP 确定 \(m\) 的每一位。设 \(f[i][j]\) 表示权值和 \(S\) 满足 \(S \equiv m \pmod{2^i}, \lfloor S/2^i \rfloor = j\) 的方案数。这里由于 \(j \le 2a_k\),DP 就很方便了。(当做位数拼凑 DP 时,从低位到高位依次考虑是一个基本的思路!)
最后再拿 bitset 优化一维枚举,可以通过。
-
-
正解构造了一个网格图,满足横向与 \(A+B\) 相关、纵向与 \(\frac{n}{A+B}\) 相关。对于每一层,都可以单独做状压 DP,有 \(2^x\) 的复杂度。那么就可以选择是纵向做状压还是横向做状压,根号平衡,最终有复杂度 \(O(2^{\sqrt{2n}}n)\)。
但是我实在不知道构造 \(A+B\) 相关网格图是怎么想得到的,所以没有实现。。。
2024.6.29 测试
-
众所周知,期望计算题有两条主要方向:
-
利用期望的线性性,进行拆解后再计算。(线性:加法 / 数乘)
-
直接利用期望定义——值 * 概率——计算。这种题一般本质是组合计数题。
而 \(\text{E}(XY) = \text{E}(X)\text{E}(Y)\) 成立当且仅当 \(X, Y\) 互相独立。
容易发现本题如果单求异或和的期望是很容易计算的:将每一位拆开单独计算即可。但涉及到的平方操作就看似较为棘手,因为每一位之间并不独立,无法利用上面的乘法性质。不过如果我们仍旧拆每一位,按照卷积展开的话,可以得到:\(s^2 = \sum s_is_j2^{i+j}\),这就依旧能够用 DP 处理概率后利用线性性计算了。
-
-
其实这道题和前面某次的 T3 几乎一模一样。只是那次求的是“长度至多为 \(k\) 的方案数”,而本题求的是“长度恰好为 \(k\) 的方案数”,而显然后者做前缀和之后即为前者。
在组合计数题中遇到“恰好”的解决方法:
-
用二项式反演,转化为钦定。但也有例外,如本题,若直接将本题中的长度恰为 \(k\) 转为钦定,二者之间是不满足二项式反演的关系式的。
-
做前/后缀和,转化为至少/至多。
我一开始一直想在不同的 \(k\) 之间做递推,但发现行不通,其关键就在不知道“不合法段的段数”上。发现段数是桎梏后,就转而枚举段数,这是一个比较好的思维过程。
另外,如果在题目中同时枚举了“段数”和“段长”,则注意“调和级数”这个东西。
-
-
遇到陌生情景的问题,我们都需要找性质。能帮助我们解决问题的性质有哪几种呢?
-
“xxx 对答案没有影响,或者 xxx 不存在不使答案更劣。”
-
“将 xxx 替换为 xxx 后,答案不劣。”
-
“xxx 部分可以通过 xxx 推出。”(这种一般用于 DP 状态优化。)
-
“xxx 仅与 xxx 相关联,或者 xxx 仅可能在 xxx 范围内。”
-
“xxx 可拆为若干可简单计算的部分。”
这道题我看到就开始套线段树,而没有找性质,或者先想想 \(O(nq)\) 暴力。所以没做出来。
题解给出的性质:
-
连续段长度对总答案没有影响。
-
当一个连续段的两端都是可以消除它的(或有一端在边界上)时,它对答案没有影响。
一个段一个段地消除,我们可以使得最后只剩一个颜色端。可以考虑用单调栈维护这一过程。(这道题的本质特点是比较函数不满足传递性,但本道题告诉我们单调栈依旧可以维护这种情况。)
然后是很神仙的线段树做法。题解将单调栈的大小表示为了一个递推式,发现我们需要维护最后一个该递推式值为 \(1\) 的位置。由于单调栈的大小必正这是比较难维护的,将其变为广义单调栈,即允许有负下标,则最后结果在递推式值最小的位置(有相同的,则取最后一个)。(不过不得不说,这种方法的拓展性太差了,本题只是因为三个元素之间的制约关系导致递推式非常简单易推。待会儿回学校看一下同校神犇的写法。)
-
-
好像需要用到一个叫做“析和树”的东西,但又还有些拓展
-
搜索剪枝。由于是计数搜索,这里用到的都是“可行性剪枝”(没有最优化剪枝)。
-
是否能够达到总和。
-
是否已经超过总和。
-
求出平局和胜局各自的总数,判断能否达到。【笑死,我不会小奥列方程组了(】
-
-
这道题妙中妙!
-
重要的性质:对于每个 \(k\),尽可能出现 \(2^k\) 或 \(-2^k\),且最多出现一次。因为出现 \(2^k, 2^k\) 或出现 \(2^k, -2^k\) 的情况都可以被替换掉。这是“替换使情况不劣的性质”。
-
如果每次搜索值域大小减半,那么搜索复杂度 \(O(n \log n)\)。
-
-
数独型搜索的惯常套路:
-
用二进制数储存行、列、宫格还能填哪些数,每次查询时就很方便,一
&
就出来了。 -
从能填方案最少的地方开填。
-
每层只用选择填一个格子,因为其它格子的情况会在后面被考虑到!
-
-
P3067 [USACO12OPEN] Balanced Cow Subsets G
一道经典的折半搜索。这道题就奇妙在它不止要用 \(3^n\) 式的搜索枚举它的划分方式,还需要二进制数储存划分方式对应的子集选取方式。
但后面卡了我许久的地方在于如何实现折半搜索的交汇部分。普通题解的做法是:对于每一个左侧的结果,枚举右侧和相同的结果,但这样实际可能被卡到 \(O(3^n)\);正确的做法是使用两个 bitset 数组(map),分别记录每个和对应的右侧集合,以及每个左侧集合能够与哪些右侧集合成功组合。
-
-
求“第 \(k\) 个”有哪些方法:
-
二分答案;
-
之前还见过一些题,比如说在 DAG 上找第 k 大路径,就是使用的求出方案数然后拼凑的方法。
-
-
比较任意两个子串的字典序关系:求出其对应后缀的 LCP 即可。
-
\(O(n^2)\) 求 LCP:
lcp[x][y] = (s[x] == s[y] ? lcp[x+1][y+1]+1 : 0)
。 -
DP 优化技巧:
-
DP 决策集合只增不减的性质;
-
转换 DP 更新顺序的优化(如本题中的倒序枚举)。
-
-
往后添加字符,字符串字典序只增不减。
-
-
写了一天这题。-
虽然说此题的标签里有 meet in the middle,但实际上我们根本不需要搜索实现,只是利用了它的思想(毕竟搜索的常数大到吓人,还很难写)。我们枚举 \(a \times 10^7 + b\) 的 \(a, b\),再进行组合即可。
-
\(10^{14}\) 是根号平衡(此题亦可称为 meet in the middle)的标志数。
-
此处我们求第 \(k\) 大数即使用的是预处理方案数,然后拼凑求解。
-
unordered_map
和map
常数太大,在可以时,尽量使用离散化。
-
-
这道题最关键的一条性质:循环节字符串的出现次数,一定为一个非 a 字符的出现次数的因数。
愚蠢的我在考场上完全没有想到。然后说起来就简单了,枚举每个因数,找到每个循环节的大致位置,再尝试往其前后添加字符 a。但由于我脑抽,就在比较每段循环节是否相同的那里用了哈希(原本不用的)——然后就被卡了。一定要慎用 自然溢出,在 \(n \ge 10^5\) 后非常容易被卡;用大质数(如 \(10^9+9, 10^9+7\))取模是最靠谱的;进制哈希的基数倒是可以随机选。这时有些时候还是会有人专门 hack,这时就可以用 “双 hash”,绝对卡不掉。
总结:
-
用因数解决循环节问题。
-
这里的复杂度是关于“因数”的。因数很小。
-
不要一看到字符串就想到自动机。有可能(比如本题)和字符串算法其实没有任何的关系。
-
2024.6.25 测试
-
本题一个关键性质是:如果考虑单个 \(|a_i - a_{i+1}|\) 在 \(l,r\) 上升时的变化情况,可以发现其可以拆成几段公差为 \(1\) 的 等差数列。
而在一个数列的 \([l, r]\) 加上一个等差数列需要用到一个叫做“二阶前缀和”的 trick,见 P4231 三步必杀。
总结:
-
二阶前缀和维护等差数列加的技巧。
-
在有众多元素会变化时,可以先对单个元素的变化情况进行思考。
-
-
其实这道题发现这一条性质就很容易了:会与子树外节点形成边的叶节点,是编号最小或最大的叶节点。
然后树形 DP,注意各种特判即可。(在动笔前,先将这些特判想清楚。)
-
众所周知,最小割最常被用来表达这样的限制:“若干费用中必须选择一个。”观察发现,本题中上行下行必须选择一种费用,符合最小割模型。
-
题目简述:给定 \(n, m\),对于所有长度为 \(n\)、值域为 \([1, m]\) 的序列求出众数出现次数之和。\(n \le 1000, m \le 10^9\)。
第一步就很难想:枚举 \(k\),求使得每一种数出现次数 \(\le k\) 的方案数。(记得前面说对“恰好”的处理方式吗?转化为前/后缀求解。)
然后后面要用到 EGF。具体地,出现次数 \(\le k\) 时的方案数为 \(n!(\sum_{i=0}^k \frac{x^i}{i!})^m [x^n]\)。接着需要求导推它的递推式。
最后可以发现复杂度是调和级别的,\(O(n^2 \log n)\)。
虽然没有实现也没有细看,但就这个思考过程,来一点总结:
-
有好多求 \(1 \sim k\) 各自为多少的这种组合计数题,其实都是调和级数。
-
恰好转“至多”“至少”。
-
生成函数求导求递推式。
-
2024.7.2 测试
-
-
\(m \le 4\),肯定与二进制压位有关联。
-
对
popcount
的大小排序进行贪心。其实我认为这里是我在考试期间给错误贪心(直接贪)举反例时,脑子晕,没举出来。。。也不知道该写什么总结,只能说让 popcount 这个概念深入一下我的脑子。
-
-
其实本身是一道非常 trival 的树形 DP + 贪心。其实考场上挂分的主要原因在 对拍。接下来我将分析一下调试过程中的对拍技巧:
-
什么时候必须要对拍?
-
观察大样例水不水。首先看数据范围小不小,再看是不是给的特殊性质(e.g. 树论题给的链)
-
观察求出来的答案是不是对全局的信息都很敏感。举个例子,本题是将每个节点的答案都求出来再在其中取最优。这时很有可能发生有地方算错了但过大样例的情况。
-
-
如何对拍?
-
可以只对拍算法的局部。有时候脑抽会想着去写一个非多项式算法去拍,但多数时候没有必要这么从头拍起。比如这是一道 DP 优化,你就可以实现 DP 原始部分,用它拍整体。
-
还是要注意上面说的,“答案不是对全局的信息都很敏感”的这种情况。这时最好将每个位置的信息都输出来,进行对拍。
-
-
-
虽然我个人认为这道题不太可做,但思考它的过程中的确有许多技巧。
-
数形结合,这其中的式子都可看作矩形的面积与周长。(虽然说其实也可以不数形结合,生推式子,但这么做可以帮助我们更容易地找性质。)
-
转化,简化条件。通过剪切,我们可以将每对合法的 \((a, b)\) 唯一标识为若干 \(1 \times 1\) 和 \(x \times y\)(\(xy \le S, x \le y\) 且 \(x\) 互不相同)的矩形之和。证明很容易:如果有两个矩形满足 \(x_1 = x_2\),就将两个矩形剪切后再相接。但是能想到这么转化真的很难,或许“试着找到唯一表示法”是一个想到这一步的途径吧。。。
-
枚举难以直接计算的贡献,快速得到容易计算的贡献。可以发现 \(1 \times 1\) 矩形对答案的贡献是 \(a \leftarrow a+1, b \leftarrow b+2\)。故如果我们对每组相同的 \(2a - b\) 找到最小的满足条件的 \(a\) 或 \(b\) 就可以了。
-
完全背包实际是转移方程满足如下条件的 DP 方程的统称:\(f[i] = \min f[k_1j+b_1]+k_2j+b_2\),用人话说就是代价函数与位置函数都满足 一次函数形式。于是,我们容易用完全背包计算最小的 \((a, b)\)。
-
-
\(n, q \le 10^5\)
好像是分块,还没写。
-
P4001 [ICPC-Beijing 2006] 狼抓兔子
本题为“最小割转对偶图最短路”的模板题。其实这个理解起来蛮简单的,观察下图:
(引自 该博客)
容易发现原图的最小割即为红边形成图的最短路。(注意 \(s, t\) 不是与原图的源汇点同向。)
什么时候可以使用这个算法呢?以下是我找到的资料:
-
平面图:能够在平面上绘制出的(即能够使得图的边互不相交的)无向图。
-
对偶图:一个平面图,对它的每一个面建一个点,然后建一条新边穿过每一条原边。(可以发现,这与上面我们使用的定义略有不同,因为整个外平面只有一个点。)
所以说,只有在一个流图为 平面图 时我们才能用这个算法进行优化。而在 OI 中,一般来说是 网格图。
-
-
最小割题。
-
这道题那个平行四边形一定为“\((\text{odd}, \text{odd}) \rightarrow (\text{even}, \text{odd}) \rightarrow (\text{even}, \text{even}) \rightarrow (\text{odd}, \text{even})\)”路径形式的结论是怎么一眼看出来的啊?……可能是最小割都是要 把限制转化为路径,然后就试着找界定路径的结论吧。
-
做了这道题以后更细地总结了一下最小割能解决问题的性质:
-
每个限制表示若干代价中必须至少选择一项,使得总代价最小。
-
将所有限制表示为形如“\((a, b, c)\)”的形式,表示在 \((a, b, c)\) 中至少选一项,如果要使用最小割问题就必须满足如下条件:如果存在两个限制 \((a, b, c), (d, b, e)\),则一定存在另外两个限制 \((a, b, e), (d, b, c)\)。
-
-
inf
边可用于将一个点分配给多个限制。
-
-
“给定一个有向图,点有点权。选择一个子图,满足如果在子图内选择了一个点就必须选择它后继的所有点。最大化点权和。
“这是一个经典的网络流问题,如果一个点被选择了则后继必须被选择,那么称该图是闭合的。因此该问题叫做最大权闭合子图问题。可以使用最小割解决。”
另外我们还需要注意最小割方案构造的问题。具体来说,我们需要先求出与 \(s\) 连通的点集 \(S\),然后取所有 \(S\) 中的点和不在 \(S\) 中的点之间的边。【注意:这并不等同于所有 \(c_i = 0\) 的边!】
2024.7.9 测试
炸裂期望场。
-
诈骗博弈论题。实际上有结论:当 \(m \ne 0\) 时,一定有先手必胜;否则先手必败。
考试时候因为担心结论不对手模了好久好久。遇到诈骗题怎么办?
-
暴力打表。
-
从结论的角度出发想证明。不要单单像瞎子摸象一样乱推。
-
-
期望概率题的基本套路有什么?
-
线性性拆分。
-
定义计算(概率 \(\times\) 值)。
-
DP 列方程,主元法 / 高斯消元求解。
这一道题就是状压 DP + 高斯消元的题目。(我们已经很久没有遇到第三种,导致有点遗忘了,就一时间没想出来。)
它还有一个很妙的点——如何压缩状态数。注意到前面直接状压 DP 其实忽略了操作的一个特性——前缀覆盖性。(优化状态数的一个好方法就是考虑题目中是否有操作的特殊性我们还没考虑到。)
所以说如果一个格子的右下角存在一个不合法格子,那么这个格子合不合法其实我们并不关心。于是对于一个等价状态类,我们只用通过记录右下角如阶梯状堆积的合法格子来代表它。这时的状态数等价于值域在 \([0,m]\) 的长度为 \(n\) 的可重集的个数,即 \(\binom{n+m}{n}\)*。
*这里我并不清楚怎么用组合意义说明,但是我可以用数学推导证明:设 \(f(n, m)\) 代表上值,注意到它有递推式 \(f(n, m) = f(n-1, m)+f(n, m-1)\),长得和 \(\binom{n}{m} = \binom{n-1}{m-1}+\binom{n-1}{m}\) 很像。运用数学归纳法,设有 \(f(n-1, m) = \binom{n+m-1}{m}, f(n, m-1) = \binom{n+m-1}{m-1}\) 成立,那么有 \(f(n, m) = \binom{n+m-1}{m} + \binom{n+m-1}{m-1} = \binom{n+m}{m}\)。
-
-
-
“恰好”转“至多”。
-
这套题中不止一次出现过维护“等价类”的思想。这道题将当前长度前缀相同的看作一个等价类,动态维护其分裂。
-
我们动态维护分裂、合并集合,复杂度往往是可均摊的。
-
还有就是它简单地应用了生成函数。对于形如 \(\prod (1+ax)\) 这种相乘式项数很少的简单生成函数,我们其实有办法在 \(O(n)\)(函数对 \(x^n\) 取模)的时间复杂度内实现其乘法、除法。
-
-
咕咕咕。
2024.7.10 测试
-
-
考虑尽量简单的结构。链 在树中是最基本的结构,是我们思考树上问题时的一个抓手。我一开始拐到二叉树上去了,我们应该先考虑这个更基本的链。
-
列方程。本题度数的关系明显是鸡兔同笼模型,可以列方程组辅助分析。还有之前我们遇到过的比赛平局、胜局、负局得分情况也可以同样分析。
还有就是以后感觉顺着一个思路怎么写也写不出来,多半是思路从头就错了。我们要有勇气从头开始。
-
-
很简单的图论二分 + BFS 判定。
一般来说图论中看见对某种路径上边取 \(\max, \min\) 都是二分。
-
咕咕咕。
-
扫描线,但是强制在线区间查询面积并。
说起来很简单:将线段树改成能够在每个区间直接维护面积的版本,并且做一下可持久化。但是实现很恶心,写完再来。
2024.7.11 测试
可能是最简单的一次,但我依旧考得很拉跨。。。
-
特别简单的贪心,主要挂在几个地方:
-
针对大样例订正后未核对先前样例是否依旧正确。
-
对于 special judge 题,没有手写 checker 验证。(checker 其实主要验证答案与方案是否相符。)
-
-
被题面诈骗用了线段树,然后去世。
-
对于 DS 题或者复杂数学题,可以先写暴力,或许有惊喜的发现。
-
以后不管写什么数据结构前,都认真地思考一下:能不能不用这种数据结构?能不能不用这种数据结构?能不能不用这种数据结构?
什么时候不用线段树?
-
对整体做查询,而且支持区间修改的操作(有一些古怪的单点修改因为要用到合并性,不可以)
-
或者对整体修改,对区间查询。
简而言之,线段树的标志是“合并”,只有这种时候我们才应该使用线段树。
-
-
线段树的常数巨大。操作较为复杂的区修区查,顶天了 \(n=q=5e5\) 时能过,稍微常数再大一点都有危险。 本题其实就刚好卡掉了线段树。
-
-
不算很难的期望题。整体难以考虑,通过线性性拆解考虑每条边的期望即可。
-
-
根号平衡。以 \(\sqrt{n}\) 为界,考虑是维护余数还是维护商。
-
用分块做到 \(O(1)\) 区间加,\(O(\sqrt{n})\) 区间查询的 Trick。其实就是在段内以及段之间做差分标记,然后每次询问的时候算一下二阶前缀和。
-
看到同班大佬还有用时间分块过了的,简单记一下这种方法吧。就是设置一个阈值 \(k = \sqrt{n}\),将最近的 \(k\) 次修改记录下来;当记录的修改数量超过 \(k\) 次时则 \(O(n)\) 重构序列;询问则在查询序列的同时算上被记录下来修改操作的贡献。
-
2024.7.12 测试
-
- 最开始的性质:将图分为若干互不相干的链,分别计算。
然后我的做法就偏暴力一些。以下为题解做法:
- “恰好”接 \(i\) 个点,可以从“至少”接 \(i\) 个点转化。
-
-
对每个子集内部求积,相互求和。这个模型可以写作:\(\prod(a_i + 1)\)。
-
如果 DP 的状态之间存在牵连关系,能使得状态数量减少。如本题,如果将状态设计为由 \(\gcd\) 表示,则能够将状态压缩到调和级数范围;还有就是区间 DP,合法的状态实际上也可以乘上常数。
-
-
-
绝对值符号需要拆开计算。
-
我考试的时候又手残加了个树状数组(还好树状数组常数小,不像上次线段树被卡炸了),完全没有必要。因为如果添加树状数组,单次“查询”的复杂度仅为 \(O(\log n)\),单次“修改”的复杂度则为 \(O(n \log n)\)。一般来说,数据结构题中查询、修改复杂度不平,说明还有优化空间、或者引入了不必要的操作。
-
-
咕咕咕。
2024.7.13 测试
-
做了这么多 T1,大致可分为两类:
-
“根据性质直接算”类。
-
贪心找性质类。
经过尝试,我们发现此题属于第二类。可以发现,我们能将答案分为若干连续段,它们之间互不相干而又可以快速计算(从这个意义上来说,这道题与前一天 T1 挺像的)。再用 并查集维护合并 即可。
-
-
贪心题。
- 线段树在同一层最多有两种不同长度的节点。
-
-
拆 \(\max\),分类讨论。
-
将一个关系式中关于同一个变量的放在一边,以更方便比较。(让我想到 dottle 的动态规划 ppt 中的“拆贡献”……)
-
线段树可以被用于维护“左侧某种数的最大 + 右侧某种数的最大”。(其实和前几天一道 Useful Algorithm 很像。)
-
2024.7.15 测试
-
看到出奇的小的数据范围,我就应该思考区间 DP,而不是硬要想线性 DP。
区间 DP 大致可分为两类:
-
操作型区间 DP。核心在于决定区间合并的顺序。此题与 T4 都属于该类。
-
值域型区间 DP。核心在于将区间分为互不相关的两部分。
区间 DP 的基础操作一定是枚举区间中的每一个断点,故复杂度至少为 \(O(n^3)\)。只有少数关于端点的或者是经过比如说四边形不等式优化的区间 DP 能够达到 \(O(n^2)\)。
本题是“调换两个区间顺序”型的操作型区间 DP。显然决策有两种:调换、不调换。但其实我们在枚举断点的过程中只需要考虑“调换”的情况,“不调换”则只需要考虑 \(l\) 和 \([l+1, r]\)。证明实际是不难的。所以说,区间 DP 有一个其独有的优化路径:考虑枚举断点操作能否被转化为一个端点操作。
还有就是本题的时间复杂度为 \(O(n^3 V^2)\),但可以通过,因为区间 DP 的常数非常小,一般来说是 \(\frac{1}{6}\)。我们不妨来具体计算一下:
\[\sum_{i=1}^n i(n-i) = n \sum_{i=1}^n i - \sum_{i=1}^n i^2 = \dfrac{1}{2}n^2(n+1) - \dfrac{1}{6}n(n+1)(2n+1) = \dfrac{1}{6}n(n+1)(n-1) \] -
-
咕咕咕。
-
T3 Pt.1、T3 Pt.2 亦 P4654 [CEOI2017] Mousetrap
很多时候,博弈论问题的解决方法不是像 DP 那样的储存计算形式,而是直截了当的贪心策略。我们不能像计算机那样一板一眼地思考,或许带一点人类的思维玩这个游戏是比较好的。(当然,就像 cyx 那天说的,这种题思维太灵活了,最好放到后面的位置做。)
这道题很妙的一个点是用“二分答案”来进行一个行动者的决策。这种方法只能用于一步决策上。
这里再提一点对拍的技巧:
-
可能一张图上有一些部分是没用的,我们可以在对拍时避免生成这些无用部分,以增大有用部分规模。
-
有时候拍的数据范围过小会造成连续几个数据都是一样的。可以用
Sleep
(单位:ms) 函数来延时。
-
-
这又是一道典型的“操作型”区间 DP。
回顾思考这道题的过程,最有启发性的是:
-
这种操作型 DP,你既然要区分两个区间之间的不同,那么在设计状态时就一定会带有一定的“顺序钦定”。比如本题,区间 \([l,r]\) 就带有 \(l-1,r+1\) 一定会被最后选的钦定性。
-
我们可以先试几种设法,看转移过程中有什么关键的量没有被存下来,再考虑将这些量也一同引入状态。比如此题,我们引入 \(r+2\) 号地雷的位置、左侧地雷都比其早选……
另外补充一点调试这种恶心细节题的小技巧:可以手算样例,问题可能就在手模的过程中出来了。不要怕麻烦。
本题复杂度尽管达到了 \(O(n^6)\),但常数特别小,具体多小我也懒得算了(考场上遇到这种,可以直接数据验证能否通过)。
-
2024.7.16 测试
-
-
先来一个线性 DP 做法。注意到如果括号无穷层嵌套的话这道题绝对得用区间 DP 来做,但是数据范围绝对不允许。所以要着手推导括号层数的限制。可以发现不超过两层,线性 DP 可解。【从数据范围限制推导需要找什么性质,用什么做法】
-
这道题还有一个简单到离谱的贪心做法:称符号全为 + 的一段为 + 段,则其实我们最多将一段 + 取反,剩下的我们全部可以取到其绝对值!(包括出题人竟然也没发现这个做法。)
所以说想 T1 时可以 从直接贪心、直接算两个角度 都想想。
-
-
假设一条路径上的边权依次为 \(w_1, w_2, \dots, w_k\),于是定义 \(S(w) = \min w_i - \max w_i + \sum w_i\) 为路径长度。请求出在这个定义下无向图中节点 \(1\) 到其余 \(n-1\) 个节点的最短路。
-
分层图解决特殊边权最短路。(这个 trick 我们练的不多,所以上一届这道题的情况普遍更好。)
-
化特殊为一般。这道题的转化很巧妙的一点是“去掉一个最大值、双倍一个最小值”实际是“去掉任意一个、双倍任意一个”的最优情况。
-
-
一个计数问题要从容斥 / 不容斥两个角度都想想。不要容斥做多了满脑子都是容斥。
其实这道题与 P8321 『JROI-4』沈阳大街 2 超级像,共性有:
-
都在解决两两匹配问题。(本题中为“人 - 组”。)
-
都通过排序来解决偏序限制。
-
都将决策权交给了限制更严格的匹配元素(如本题中的“组”有人数限制),而记录还剩几个另一种元素。
-
-
咕咕咕。
2024.7.18 测试
-
看到这个数据范围就不应该花太多时间在贪心上,这明显是 DP 啊!
不难列出一个
bool
型的 DP:设 \(f[i][x][y][z]\) 表示在填第 \(i\) 匹马时三类条件分别满足了 \(x,y,z\) 个,是否有可能。对于一个bool
型的 DP,我们一般有两种优化方案:-
bitset 优化。
-
将某一维转化为最值,存在数组中。
用第 2. 种方法即可过掉该题。
我这个弱智手欠多加一维,导致代码复杂,调了 2h,但这道题的难度应该是 30min ~ 1h 的。。。所以说思维过程还是不够快、准、狠。
但是这个手欠过程还是有收获。因为这种方法要炸空间,所以我使用了滚动数组。值得注意的是:滚动数组最多滚动一维。
-
-
基本思路其实不难。先通过经典套路质因数分解,容易发现每个数可以根据其质因子的指数模 3 的余数划分为若干等价类,且能与其形成完全立方数的完全属于另一个等价类。用
map
维护一下就好了。设 \(p(n)\) 表示 \(n\) 以内质数的个数,复杂度为 \(O(n p(\sqrt{V}))\)。然而这是可以被卡掉的。题解给出了一种非常巧妙的 \(O(n p(\sqrt[3]{V}))\) 做法:我们可以暴力质因数分解直到 \(p > \sqrt[3]{V}\)。此时 \(a_i\) 还剩下的部分最多还有两个质因子,我们即可直接将其乘入等价类;经过简单的分类讨论,我们也可以快速算出它对立的等价类。
总结:
-
这一步优化本质是因为我们并不关心质因数分解的具体值。
-
越大的因数个数越少。我们可以以 \(\sqrt[k]{x}\) 为界划分因数个数,不仅仅是 \(\sqrt[2]{x}\)。
-
关于
map
的小知识:当我们访问到它的任何一个下标时 (不管有没有对这个下标做修改)它都会新建一个节点。所以有时候要避免建立无用节点,我们可以用mp.find
来帮忙。
-
-
就……不知道出题人怎么想的,扔了一道字符串板子题在这里。可以用 SAM 或者 SA + 二分通过。
题解里面给出的是很妙的根号分治做法——通过查询串的长度来做划分,复杂度 \(O(n \sqrt{n \log n})\)。
-
咕咕咕。
2024.7.19 测试
NOIP2023 真题。
其实做完以后感觉除了 T3 以外整体思维不难,偏码力题。而 T3 又过难,还没设计充分的部分分,导致不好区分中高档选手和中档选手。(但也有可能本身省选才是这两类选手的分水岭吧。)
-
不算很难的贪心。将每个字串分别按照最大字典序、最小字典序排列,再求出最小的最大字串(注意也要求出次大的,为了特判最小的最大字串对应的最小字串),与最小字串比较即可。
-
图论建模 + 二分图判定(类似)。
我主要的问题出在调试过程。当时我就应该手造数据调试,而不是在那里干瞪眼,导致浪费了不少时间。
另外就是如何手动开栈:
-Wl,--stack=SIZE
,其中SIZE
是以字节为单位的。NOIP 的常见栈空间大小为 512MB,即 536870912B。 -
神奇贪心题,我不会。
-
线段树优化 DP,还挺套路的,就是代码比较恶心。
中间调试我很久的一点是
long long
的问题。因为实际上 c++ 中类型强制转换发生在一个二元运算符的两侧,而不是我之前以为的会直接转换整个运算式。所以说两个大int
相乘一定要乘上1ll
;而且我们要尽量将long long
变量置于和式的开头。更多内容见:奇妙的 c++ 混合运算式。