25 秋 总结
9.2
P11619 种南瓜
Tag:线段树分治,线段树。
犯的错误有:
stack中使用的数据类型为int而非long long导致极大值变化,进一步导致答案错误。- 存边集的
vector的大小没有 \(\times 8\) 导致数组访问越界。
之后在 RE 的情况下一定要检查到位所有数组包括 STL 的数据范围。如果发现极大值范围从 long long 变回 int(比如 0x3f3f3f3f3f3f3f3f 突然降为 0x3f3f3f3f)则要注意所有开 int 的位置有没有出错(甚至包括循环变量这种细节)。
CF303E Random Ranking
Tag:概率 DP,DP 优化,分治,卡常。
犯的错误有:
- 输出答案时把 \(ans_{i,j}\) 打成 \(ans_{i,j-1}\)。
- 如果题目卡常,或者精度要求不高的情况下,最好把
long double删去,改为double。 - 数组不要开得太极限(紧挨),否则要判断越界的情况(直接跳过)。
查错时不要漏掉输入输出这种细节。除非精度误差要求控制在 \(10^{-10}\) 以内甚至更低,否则尽量少用 long double。数组不要开得太极限(就算在题目卡常的情况下),至少也要 \(+5\)。如果需要开两三倍但题目时间或者空间刻意去卡(即出题人刻意卡常)那么需要特判一下会越界的情况。
9.3
P1856 矩形周长 Picture
Tag:扫描线。
是的你没看错,模板题也会错这么多。
犯的错误有:
push_up函数中 \(0/1\) 写反。- 统计答案时混用线段树中存储的信息(具体的,将
.len错写成.sum)。 - 计算最小值的变量
Mn没有赋初值为极大值0x3f3f3f3f而是直接使用的全局变量自动初值 \(0\),导致兀余移位。 - 手残把
R>mid错写成R>mid+1(也等同于是说,少打了一个=)。
注意细节,尤其复制粘贴后直接修改的代码段难免会出现细节问题。统计答案是想清楚到底要使用记录的哪个数据。计算最大最小值的变量记得给初值。注意 > 和 >= 的区别,< 和 <= 的区别(手残党常犯错误)。
CF2051E Best Price
Tag:排序,二分。
好奇问一下为什么这个题在扫描线的题单里。这跟扫描线有关系吗?
没犯什么错。太水了。
CF1884C Medium Design
Tag:离散化,差分(线段树也行,但是更麻烦)。
没犯什么错。
CF612D The Union of k-Segments
Tag:差分,差分变种,思维,构造(?)。
犯的错误有:
lower_bound最后多敲了个 \(-1\) 导致最后对应回来的时候数值错误。- 由于是在坐标轴上差分,正常的
c[l]++,c[r+1]--;要修改为c[l]++,c[r]--。
lower_bound 和 upper_bound 都是不要 \(-1\) 的,要 \(-1\) 的那个是用来离散化去重的 unique!别总搞混。还有,如果差分的场景不同,要注意 \(+1\) 和 \(-1\)(包括 \(0\))的变化。
CF1320C World of Darkraft: Battle for Azathoth
Tag:线段树,双指针。
犯的错误有:
- \(n\)、\(m\) 写反。
- \(ans\) 未初始化为极小值而是直接保留全局变量统一值 \(0\),导致无法让负数成为答案。
- 线段树建数时误将 \(a\) 数组建树而不是正确的 \(val\) 数组。
\(n\)、\(m\) 的含义一定要搞清楚,避免写反。注意所有求 \(\min\) 和 \(\max\) 的变量的初值,通常来说求 \(\min\) 的要赋极大值,求 \(\max\) 的要赋极小值。线段树建树之前搞清楚到底是建哪个数组的树,防止习惯性对某种名称(比如比较常用的,\(a/b/c/p\) 之类的)的数组建了树,进一步导致线段树所维护的东西完全混乱,到头来答案就会出错。
9.5
P1379 八数码难题
Tag:BFS,记忆化搜索,Hash。
思路就是一个暴力,特别 Easy。
代码实现没难度,因此没犯什么错。
CF1225D Power Products
Tag:质因数分解,Hash。
犯的错误有:
- 在质因数分解时,错将质因数之值作为 Hash 底数,导致 Hash 冲突的概率变高。
之后 Hash 的时候记得想清楚用什么东西来 Hash,最好是有一个 \(Base\) 并且不要用常用的(别的还好,CF 可就万万不行了),比方说 \(998244353\) 和 \(10^9+7\) 这种的,就千万不能用。特殊情况下可能还需要上随机数 Hash。
CF1418G Three Occurrences
Tag:Hash,随机数 Hash,双指针。
犯的错误有:
- 用于生成随机数的函数用的是
int类型而非long long类型(即,mt19937与mt19937_64的区别)。 - 由于生成随机数时取模后没有 \(+1\),导致生成出来的 Hash 函数可能为 \(0\),进而使 Hash 无意义,因此答案会错误。
之后生成随机数要注意,如果数据范围支持就用 mt19937_64 吧,生成范围会大得多。生成随机数 Hash 并且取了模的话一定一定一定要 \(+1\),不然出现 \(0\) 就完蛋了。
题解。
题目就是说,给定一个长度为 \(n\) 的序列 \(a\),问你有多少个子区间满足这个子区间中出现的所有数字都恰好在这个子区间里出现了 \(3\) 次。
考虑双指针,枚举 \(r\) 并统计有多少个满足条件的 \(l\)。
由于直接卡死出现恰好 \(3\) 次不好搞,因此拆分成下面两个条件:
- 在这个子区间中,所有数字的出现次数都是 \(3\) 的倍数。
- 在这个子区间中,所有数字的出现次数都不超过 \(3\) 次。
上述两个条件综合起来,那么每个数字的出现次数就只能是 \(0\) 或者 \(3\) 了。
考虑如何实现上面两个条件的判断。
条件 \(1\) 可以用桶记录每种数字的出现次数对 \(3\) 取模的结果,那么在判断的时候,如果区间 \([l,r]\) 满足条件 \(1\),那么前缀 \(l-1\) 的桶和前缀 \(r\) 的桶肯定是一模一样的(这就类似于前缀和嘛)。但是,很显然,我们不能 \(O(n)\) 去判断,因此可以考虑把它 Hash 一下,弄成一个整数然后塞进 map 里,这样就方便多了。当然,CF 喜欢卡比较常用的 Hash 函数,因此我们可以采用随机数 Hash 的方式。
条件 \(2\) 该如何处理呢?也很简单,由于新加入一个 \(r\) 之后,如果之前的区间都维护好了,那么只有 \(a_r\) 这个数的出现个数可能会超过 \(3\)。怎么办?简单,咱不是枚举了一个 \(l\) 吗,这就代表当右端点为 \(r\) 时最靠左的满足条件 \(2\) 的左端点,那么如果 \(a_r\) 超限了,肯定不能删掉 \(r\) 嘛,那就从左边下手,用 while 枚举直到 \(a_r\) 的出现次数满足要求,当然,map 里边存的东西也要相应去减少,毕竟如果 \(l\) 已经不能当左端点了,那么你也不能把它的信息放进 map 里了,否则会统计进去不满足要求的答案。
代码实现还是比较 Easy 的。
时间复杂度 \(O(n \log n)\),那个 \(\log\) 是 map 的消耗。
CF1175F The Number of Subpermutations
Tag:Hash,异或 Hash,随机数 Hash。
犯的错误有:
- 计算答案时错误使用了每个数字对应的 Hash 函数表示排列异或值,实际上要使用 Hash 函数的前缀和数组中所记录的数值(简单来说,就是把 \(g\) 敲成了 \(Hsh\))。
- 注意位运算的优先级,
^的优先级是很低的,如果要写if(s==x^y)来判断 \(s\) 是否等于 \(x \oplus y\),需要加上括号变为if(s==(x^y))。
搞清楚每个数组的定义,使用时想清楚需要使用的到底是什么。注意位运算的优先级,不确定时最好打上括号。
AT_abc250_e Prefix Equality
Tag:离散化,Hash,异或 Hash,随机数 Hash。
做多了 CF 的题,什么都想上随机数 Hash 怎么办?随机数 Hash 太方便了(
好久没见过这么板的题了,只可惜一开始还是挂了下。
犯的错误有:
- 读错题,以为要判断的是数字的可重集合是否相等,实际上要判断的是集合是否相等(要去重!),因此一开始没用
map去标记。
之后读题认真点。
话说回来可重集合好像就不能用异或 Hash 了啊,如果这个序列中 \(2\) 出现 \(1\) 次而另外个序列中 \(2\) 出现 \(5\) 次,其他全相同,也会给你判成相等啊,因为异或两次会抵消呐。
吐槽 AT 人机验证搞了好久都没登上去,以至于下课了都还没登上去,回家后补交的。
9.6
P2964 A Coin Game S
Tag:前缀和,DP,博弈 DP(零和博弈)。
犯的错误有:
- 计算 DP 数值时没有用前缀和 \(s_i\) 去减,导致所有 DP 值都是 \(0\),答案也变成 \(0\)。
下次博弈 DP 的时候一定要记着,如果是零和博弈,一般都要用前缀和(或者是某个什么和)去减更新过来的状态,要不然就没得答案了。
题解。
题目就是说,有 \(n\) 枚硬币,依次从左到右编号为 \(1 \sim n\),并且每枚硬币有一个价值 \(a_i\)。现在有 A 和 B 两个人,轮流从剩余硬币的最左边取出连续一段硬币,并将这些硬币的价值之和加进自己的硬币价值总和中。如果上一个人在上一轮取出了 \(x\) 枚硬币,那么当前这个人这一轮最多可以取出 \(2 \times x\) 枚硬币。A 先手,游戏最开始最多可以取出 \(2\) 枚硬币。假设两人都足够聪明,两人的目标都是最大化自己的硬币价值总和,问 A 的硬币价值总和最大是多少。
由于正着做需要记录当前先手是 A 还是 B,而且 \(\times 2\) 等限制会导致非常其非常麻烦,因此我们考虑倒过来,两个人轮流放硬币,并且如果上一轮那人放了 \(x\) 枚硬币,这一轮这人放了 \(y\) 枚硬币的话,必须满足 \(y \times 2 \ge x\)。
为了方便记录和转移,输入时可以倒着输入。然后算一个前缀和 \(s\)。
接着开始 DP。首先定义 \(dp_{i,j}\) 表示当前已经放了 \(i\) 枚硬币,并且接下来得放 \(x\) 枚硬币(即,当前这人最多放了 \(2 \times x\) 枚硬币)的情况下,当前这一轮放硬币的人的最大价值之和是多少。
转移时枚举 \(k\) 表示当前这一轮这个人放了多少枚硬币。转移 \(dp_{i,j} = \max \{ s_i - dp_{i-k,k} \}\)。
这样做的时间复杂度是 \(O(n^3)\) 的,在 \(n = 2 \times 10^3\) 的情况下完全无法接受。考虑优化。
发现 \(dp_{i,j}\) 的值是完全包含 \(dp_{i,j-1}\) 的,那么每次枚举的时候首先先继承 \(dp_{i,j} = dp_{i,j-1}\),那么只需要考虑 \(k = j \times 2 -1\) 和 \(k = j \times 2\) 的两种情况了,这样子时间复杂度就变成 \(O(n^2)\) 级别的了。
最终答案即为 \(dp_{n,1}\),也就是当前已经放了 \(n\) 枚硬币并且下一人得放 \(1\) 枚硬币,转化一下就是当前这人最多放了 \(2\) 枚硬币,满足第一轮开始的要求。
代码很短。
CF936B Sleepy Game
Tag:图论,思维。
犯的错误有:
- 在已经求解出一组答案之后继续 DFS 遍历而不是直接跳过,导致 \(Ans\) 统计多余。
之后如果遇到这种“有多组答案,输出任意一组均可”的这种题目的情况下,如果你已经记录了一种答案了,之后的遍历完全跳过就 OK 了,防止多余统计导致答案混乱。
题解。
题目就是说,给定一张 \(n\) 个点 \(m\) 条边有向图以及一个起点 \(St\),如果可以从 \(St\) 出发移动奇数步到达一个叶子节点(此处叶子节点代表出度为 \(0\) 的节点)那么输出 Win 并给出移动方案,否则要是可以从 \(St\) 出发移动到一个环那么输出 Draw,都不行输出 Lose。
分三种情况,Win、Draw 和 Lose 分别讨论。
Win的情况:Win的情况其实直接搜索一遍就能够解决,但是要注意不能绕进环里边了。- 具体的,可以定义 \(p_{i,j}\) 表示节点 \(i\) 可否在步数为 \(j\) 的情况下到达。当然,空间不支持 \(O(n^2)\),但我们只关心 \(j\) 的奇偶性,因此不妨让 \(j\) 的位置放一个 \(j \bmod 2\),这下子空间就没毛病了。
- 遍历的时候传俩参,一个 \(u\) 一个 \(c\) 分别表示节点编号以及当前 \(j\)(对 \(2\) 取魔),然后每次进来的时候记一下就行。
- 至于记录路径的话,这个简单,直接塞一个
vector里边就成了。
Draw的情况:- 如果存在
Win的情况,那么Draw的情况就不重要了。 Draw的情况的主要核心就是判断可否从 \(St\) 出发顺利抵达一个环。而如何判环,则是这种情况的核心。- 考虑定义一个 \(vis\) 数组记录节点 \(u\) 的遍历情况。具体的,如果 \(vis_u = 0\),那么表示 \(u\) 还没被遍历到;如果 \(vis_u = 1\),那么表示 \(u\) 已经被遍历到了,但是它的所有相邻节点还没遍历完;如果 \(vis_u = 2\),那么表示 \(u\) 和它的相邻节点都已经被遍历到了。
- 这个时候判环就非常方便了——如果遍历到一个 \(u\),并且它的某个相邻节点 \(v\) 有 \(vis_v = 1\),那么肯定就存在环了!
- 在具体实现的时候,
Win和Draw的判断条件可以塞进一个 DFS 里头。
- 如果存在
Lose的情况:- 这就不用多说了吧,当
Win和Draw的情况都不存在时,就是Lose咯。
- 这就不用多说了吧,当
具体实现的话可以用一个 \(Now\) 记录当前遍历到的状态,\(Now = 2\) 表示已经发现了 Win 的路径了,\(Now = 1\) 则表示已经发现了 Draw 的路径,而 \(Now = 0\) 则表示当前状态就是 Lose。
代码还是比较好写的。
CF2005E1 Subtangle Game (Easy Version)
Tag:博弈 DP,二维后缀和。
犯的错误有:
- 输出答案时错将 \(dp_{1,1,1}\) 作为答案索引输出,实际上所有 \(dp_{1,i,j}\) 的情况都要考虑到。
- 敲错维度,比方说 \(dp_{x,i,j}\) 敲成 \(dp_{i,j,x}\) 这种。
DP 题要想清楚答案到底是啥,要不要遍历计算,或者直接一个数值就是答案。定义多维 DP 数组(尤其在三维及以上的时候),一定要搞明白每个维度表示什么,防止敲反敲乱敲错。
CF1895E Infinite Card Game
Tag:离散化,博弈 DP,图论。
犯的错误有:
- \(x\)、\(y\) 写反。
- 多测清空时应该清空到 \(n+n+m+m+1\),但我只清空到了 \(n+m\)。
- 多测清空时清到 \(n+n+m+m+1\),这个数很大会 RE,因此要和 \(10^6\) 取 \(\min\)。
之后写代码时速度慢一点,搞清楚 \(x\) 和 \(y\),\(u\) 和 \(v\),\(a\) 和 \(b\),\(p\) 和 \(q\) 这种易混淆的字母分别代表的含义,想清楚哪里要用哪种含义的变量。多测清空的时候务必想清楚清空的范围,看看要不要翻倍,要不要 \(+1\)。
9.8
CF2037F Ardent Flames
Tag:二分,离散化,扫描线 / 差分。
犯的错误有:
- 排序时关键字顺序错误,如果 \(first\) 相同那么需要按照 \(second\) 从大到小排序,而并非常规的从小到大。
- 指定某个 \(p\) 去
check时,存在某个敌人不可能被打败的情况,这种情况下不能放入区间进行计算。
对某个数列什么的排序的时候要注意具体按照的什么顺序。一切都要考虑“不可能”、“不存在”、“不可行”这种类型的状态,这种情况下有的题目需要特殊处理,有的题目直接跳过就行。
题解。
题目就是说,有一条长度无限的数轴,上面站着 \(n\) 个敌人。给定这 \(n\) 个敌人分别所站的位置 \(x_i\) 以及他的生命值 \(h_i\)。你拥有攻击力 \(m\)。如果你选择站在 \(p\) 位置去攻击,那么敌人 \(i\) 的生命值会下降 \(\max(0,m-\lvert p-x_i \rvert)\)。如果你选定一个位置,你只能待在那里一直攻击敌人。现在你可以选择一个最优的位置 \(p\),并且你的目标是使至少 \(k\) 位敌人的生命值 \(\le 0\),问你至少要攻击多少次。
显然,这个攻击次数具有单调性,因此考虑二分。
那么问题转化为,给定攻击的次数 \(s\),问存不存在一个位置 \(p\) 使得可以在 \(s\) 次攻击以内击败 \(\ge k\) 名敌人。
可以看出,对于每个敌人 \(i\),只有当你站在区间 \([x_i - m + \lceil \dfrac{h_i}{s} \rceil , x_i + m - \lceil \dfrac{h_i}{s} \rceil]\) 中的某个位置的时候,才会在 \(s\) 此以内击败这个敌人,否则想都别想。
问题进一步转化,变为,判断是否存在一个点 \(p\) 使得其被至少 \(k\) 个区间覆盖。
考虑采取扫描线的思想,弄一个 pair 类型的数组,对于每个敌人,首先把他所对应的区间 \([l_i,r_i]\) 算出来,然后往这个数组里塞两个二元组 \((l_i,1)\) 和 \((r_i,-1)\)。然后优先按照 \(first\) 从小到大,其次按照 \(second\) 从大到小排序。遍历这个数组,用一个 \(now\) 每次加上 \(second\)。如果某个时刻 \(second \ge k\) 了,那么直接返回“通过”就 OK 了。
显然,如果所有前缀都失败了,那么返回“不通过”即可。
时间复杂度 \(O(n \log V)\),其中 \(V\) 为 \(x_i\) 的值域。
CF610D Vika and Segments
Tag:扫描线。
板子题,稍改一下即可。
犯的错误有:
- 没有将 \(y_1\) 和 \(x_2\) 的值进行修改,导致 \(ans\) 总是为 \(0\)(即,不计入一条线的情况)。
- 没有考虑 \(x_1>x_2\) 以及 \(y_1<y_2\) 的情况,导致顺序错乱。
下次要注意,如果要对一条线统计面积,一定要修改一些值不然根本计算不进去。如果题目没有明确说明顺序正确,那么最好还是判断一下顺序,需要 swap 的就 swap 一下,避免酿成大错。
CF35E Parade
Tag:离散化,扫描线,线段树。
犯的错误有:
- 空间兀余,导致 MLE(本身题目也特意去卡了你)。
- 由于此题特殊性质,不能套用板子,因此线段树中不存在
push_up函数。 - 注意不要对叶子结点进行
push_down不然会导致空间翻倍。 - \(Left\) 误赋初值为 \(0\) 而非正确的 \(1\)。
change函数传参错误,应对 \(r_i\) 进行 \(-1\) 而不是原封不动直接传参。
如果题目空间限制较为特殊(其实其他情况下也可以好好分析分析),则要注意空间最好不要多开,用多少开多少,如果需要的话还可以动态开点。考虑题目特殊性,可能不能直接套用算法板子,会对其进行删减、增添与修改。在树上时最好不要访问叶子结点的所谓“儿子”不然会数组越界。注意赋值,传参,考虑边界情况是 \(0\) 还是 \(1\),考虑要不要对原值进行 \(+1\) 或 \(-1\)。
9.9
CF1140F Extending Set of Points
Tag:线段树分治,并查集,二分图,思维转化。
犯的错误有:
- 混淆题目给定变量 \(n\) 和自定义常量 \(N\)(即,使用变量时没有想清楚当前需要使用的是元素个数 \(n\) 还是值域范围 \(N\)),导致代码逻辑混乱。
- 并查集未赋初值导致 \(Ans\) 均为 \(0\)。
- 处理范围错误,应该是 \(2 \times N\) 而非 \(N\)。
- 线段树分治最开始存点时
Add函数判断在左在右时只考虑了全区间在左或者全区间在右的情况(简单来说就 \(L\) 和 \(R\) 写混了 TAT),导致很多区间没有计入统计,进而导致答案错误。
遍历、计算、偏移,不论什么情况下,一定要考虑清楚到底要用哪种含义的变量,不要让代码逻辑过于混乱。记得赋初值,并且遍历的范围别搞乱了,要翻倍的翻倍,有的翻 \(3\) 倍也可能。注意,除非查询的是一个点,不然线段树两边都有可能跑,别瞎搞吧 \(L\) 和 \(R\) 写反这种的。
CF601E A Museum Robbery
Tag:线段树分治,\(0/1\) 背包。
犯的错误有:
- \(v\)、\(w\) 写混导致逻辑混乱。
- 注意用来表示无解的数值可行否,有的时候 \(0\) 是行不通的可能要用 \(-1\) 甚至极小值之类的。
注意别再变量写混了这样真的很难调好吗。表示无解的数值一定要确定在有解的情况下 \(100\%\) 不会存在出现这个数值的情况,哪怕只有 \(0.0001\%\) 甚至更小的概率,你也得换一个。
题解。
题目就是说,一个博物馆正在展出展品。最初有 \(n\) 件展品,每件展品都有一个价值 \(v\) 和重量 \(w\)。接下来,工作人员会执行 \(q\) 个事件,可能是增加一件展品,可能是撤下一件展品,也可能是查询以下式子的值(其中 \(p = 10^7 + 19\) 且 \(q = 10^9 + 7\)):
而 \(s(m)\) 的定义为,从当前展出的展品中任意挑选一些展品,但是要求这些展品满足 \(\sum w \le m\),那么 \(s(m)\) 的值即为 \(\sum v\) 的最大值。
首先,显然的,有加入有删除(最初的展品也可以并到事件中当作是增加一件展品),算出出现的时间区间后,就可以通过线段树分治维护了。具体是线段树分治中计算答案的部分该怎么办。
可以看出 \(s(m)\) 就是在计算一个容积为 \(m\) 的容器可以装下的当前展品的最大的价值之和,也就是一个 \(0/1\) 背包。
那么可以利用这一个区间里存下的出现情况,新增进去。而我们都知道,新增一个物品然后算本来的所有物品加上当前这个物品的 \(0/1\) 背包时,如果你知道本来的物品算出的那个 DP 数组,那么在这里新增后只需要 \(O(S)\) 的时间就可以搞定(\(S\) 表示容器的容量)。
那么我们就可以采取这个方案!唯一的问题在于你得把上一次的 DP 数组也给弄进来。其实没什么大不了的,直接在递归函数里传一个数组做参数就 OK 了。
注意最开始的时候计算出现区间会比较复杂,因为出现区间的总长度会是所有事件中查询的长度,因此需要用 \(Cnt\) 记录一下当前到了哪个区间段。
然后就做完了,不是很难。
9.10
P5459 回转寿司
Tag:离散化,树状数组。
犯的错误有:
- 代码中,树状数组里从头到尾都没有加入过 \(X\) 的信息,但是代码中却尝试将 \(X\) 删去,导致树状数组存储的值变得混乱。
当你要删去一个东西的时候,一定要保证它之前加入过,要不然你是不能执行删除操作的。
CF641E Little Artem and Time Machine
Tag:离散化,树状数组,分层(?这叫分层吗)。
犯的错误有:
- 由于是离线处理,而题目只要求对 \(opt=3\) 的操作输出答案,因此输出时要判断一下,当且仅当 \(opt=3\)(换句话说,\(ans\) 数组中存储了真正有效的数值)时才输出 \(ans_i\)。
离线处理操作询问时,如果不保证每次操作后都要输出东西,那么一定要判断这一回合要不要输出。最简单的办法当然是判断存储答案的数组中在这一回合是否存储了真正有效的答案。
P4093 序列
Tag:CDQ 分治,CDQ 分治优化 DP。
犯的错误有:
- 更新 DP 数组的值时直接使用索引 \(i\) 而不是当前排过序后的 \(tmp_i\),导致 DP 值更新错误。
DP 状态转移方程一定给我写明白了,写纸上了就给我抄明白。
就一个 DP 的转移方程,写草稿纸上了还抄都抄不对,太菜了。
CF785E Anton and Permutation
Tag:二分,分块。
为什么有这么多和 CDQ 分治搭不上边的题呢?
犯的错误有:
- 更新的时候多
swap了 \(bel\) 值,实际上由于只是换了数值,索引所对应的“块”的编号其实上是不会变的,所以不需要对其进行swap,只需要swap两个具体数值就 OK 了。
更新的时候想明白到底啥要更新,啥不要更新。
9.12
CF1036C Classy Numbers
Tag:数位 DP。
没犯什么错。所以记录它的意义是?
P4124 手机号码
Tag:数位 DP。
没犯什么错。所以记录它的意义是?
喀,总结内容完全抄袭上一道,举报!(
P3413 SAC#1 - 萌数
Tag:数位 DP。这不废话。
致敬第一道不看题解切出来的紫题!
犯的错误有:
- 取模不完全导致出负数。
一定要记得取模完全 /kel 一点漏洞都不能留!
题解。
题目就是说,给定一个范围 \(L \sim R\),问你这中间存在多少个数,满足其数位上存在长度 \(\ge 2\) 的回文串。由于答案可能很大,请对 \(10^9+7\) 取模。
显然,如果直接去判断是否存在长度 \(\ge 2\) 的回文串是不可行的,这样记录起来未免过于麻烦。
那么考虑转化一下。注意到如果一个回文串的长度为奇数,那么其显然其包含一个长度为 \(3\) 的回文串,形如 \(\text{aba}\);反过来,如果一个回文串的长度为偶数,那么其显然包含一个长度为 \(2\) 的回文串,形如 \(\text{aa}\)。
那么维护起来就方便多了,我们只需要考虑连续的三个数字是多少就好了。
显然数位 DP,定义 \(dp_{i,x,y,s,u,k}\) 表示当前考虑到第 \(i\) 位,上下界标记是 \(x\) 和 \(y\),\(s\) 表示当前是否已经出现过回文串,\(u\) 表示上一位的数字,而 \(k\) 表示当前这一位的数字。
转移,我采取的是扩散型。
首先 \(i,x,y,s,u,k\) 都要枚举,然后还得枚举新一轮的数字 \(j\)(当然,范围要根据 \(L\) 和 \(R\) 已经当前的上下界标记 \(x\) 和 \(y\) 对应算出来)。
可以很轻松地计算出新的 \(x\) 和 \(y\),而新的 \(s\) 也只需要根据原 \(s\),以及 \(u,k,j\) 相互之间的关系得出。那么转移式就是 dp[i+1][nx][ny][ns][k][j]+=dp[i][x][y][s][u][k] 了(其中 \(nx,ny,ns\) 分别代表新的 \(x,y,s\))。当然,要记得取模。
但是这样你却发现过不了样例!这是为什么呢?仔细一分析,原来是前导零搞的鬼。前导零不算入,肯定不能根据前导零判断回文呐!
怎么办呢?我这里采取了一个很智障的做法,首先把 \(0 \sim 9\) 的所有数字全都 \(+1\),变成 \(1 \sim 10\),然后用 \(0\) 来代表前导零。
转移的时候,求解新的 \(s\) 时,就要注意判断一下了,它们的值如果为 \(0\),那就万万不可计入统计!
这样的话,第一个样例确实顺利通过了,但是你又挂在第二个样例上了!为什么会发生这样的事情?因为,如果你枚举一个 \(L\) 还处在前导零的位置,你会发现 \(j\) 中既枚举到了前导零对应的 \(0\),也枚举到了正常的 \(0\)!
这样可不行,一个位置怎么能出现两个 \(0\) 的情况!显然,如果这里处在 \(L\) 中的前导零位置,那就不应该枚举到正常的 \(0\),而是直接跳过枚举 \(1\)。
如何解决?其实上很简单,在循环的时候,不是有一个地方 j++ 吗。这里特殊判断一下,如果 \(j=0\)(表示 \(j\) 当前代表前导零的 \(0\)),那么这一次就不是 j++ 了,而是 j+=2。不过,在我的代码中,我使用了一个函数来处理。
那么这样你就会发现你通过了第二个样例,并顺利 AC!
AT_abc154_e Almost Everywhere Zero
Tag:数位 DP。
不长脑子的板题,怎么可能犯错呢。
AT_abc135_d Digits Parade
Tag:暴力枚举,(类)数位 DP。
没犯错。因为这是个垃圾题。
AT_abc194_f Digits Paradise in Hexadecimal
Tag:数位 DP,状态压缩。
没犯错因为题太无聊。
9.13
CF691E Xor-sequences
Tag:二进制 popcount,矩阵乘法,矩阵快速幂。
犯的错误有:
- 错误地直接判断了 \(\text{popcount}(x)\) 是否为 \(0\) 以计算转移矩阵,实际上要判断的是 \(\text{popcount}(x) \bmod 3\) 的值是否为 \(0\)。
下次判断的时候搞明白了,具体是要判断什么东西是否为哪个数值。
CF551D GukiZ and Binary Operations
Tag:二进制,二进制拆位,矩阵乘法,矩阵快速幂。
没犯什么错。
题解。
题目其实上就是,问你存在多少个长度为 \(n\) 的数列 \(a\),满足其中所有数字均为非负整数并且 \(< 2^l\),还得满足 \((a_1 \operatorname{and} a_2) \operatorname{or} (a_2 \operatorname{and} a_3) \operatorname{or} \dots \operatorname{or} (a_{n-1} \operatorname{and} a_n) = k\)。由于答案很大,因此请对 \(m\) 取模后再输出。
由于 \(\operatorname{and}\) 和 \(\operatorname{or}\) 都是位运算,那么,很显然,在二进制下,每一位都是独立的。
因此可以考虑分别计算这 \(l\) 位,随后把答案相乘即可。
那么现在问题就转化为,问存在多少个长度为 \(n\) 的数列 \(a\),满足其中所有数字均为 \(0\) 或 \(1\),并且 \((a_1 \operatorname{and} a_2) \operatorname{or} (a_2 \operatorname{and} a_3) \operatorname{or} \dots \operatorname{or} (a_{n-1} \operatorname{and} a_n) = k\)(其中 \(k\) 也是 \(0\) 或 \(1\))。
可以发现,只要存在一个 \(1 \le i < n\) 满足 \(a_i = a_{i+1} = 1\),那么这个数列的值就肯定是 \(1\) 了。
我们考虑直接 DP。定义 \(dp_{i,j,s}\) 表示当前考虑到第 \(i\) 个数且 \(a_i = j\),以及之前是否(用 \(s\) 的 \(0/1\) 表示)出现过相邻两个 \(a\) 值都为 \(1\) 的情况。
根据定义,可得以下转移方程:
然后发现这个玩意儿可以直接套进矩阵里。具体的,可以把其转化为以下形式:
那么就可以做到 \(O(l \times 4^2 \log n)\) 的时间复杂度了,其中 \(4^2 = 16\) 是矩阵的大小。当然你也可以视其为常数,因为确实很小。
AT_abc256_g Black and White Stones
Tag:快速幂,排列组合,组合数,矩阵乘法,矩阵快速幂。
没犯什么错。
P2579 沼泽鳄鱼
Tag:矩阵乘法,矩阵快速幂,矩阵优化图上 DP,邻接矩阵,最小公倍数,周期性。
犯的错误有:
- \(i\)、\(j\) 写混。
- 没有为矩阵设置 \(n\) 和 \(m\) 值导致执行乘法时啥也没干。
- 偏移数值时没有偏移完全。
- 修改邻接矩阵时没有想清楚具体要修改哪些位置。
搞清楚变量的含义,不要总是顺手写成 \(i\)。记得赋初值,包括 DP 初值和矩阵大小等。偏移数值时注意如果偏移了 \(x\) 那么 \(y\) 也要偏移,决定偏移就不要漏否则干脆别偏移。更新、修改时根据其本质考虑清楚到底该如何进行更新或修改。
题解。
题目啰里吧嗦说了一大串,其实上就是,给定一个有 \(n\) 个点 \(m\) 条边的无向图,然后里面还有 \(F\) 只怪兽,它们的行进都有一个周期性并且一个周期的长度为 \(2 \sim 4\),从一个点到另一个点,再到另一个点,然后绕回来这之类的。现在有一个小人,初始位于点 \(St\),问有多少种方案,使得这个小人走恰好 \(k\) 步之后到达点 \(Ed\) 并且不会被任意一只怪兽攻击(一只怪兽会攻击这个小人,当且仅当它们在同一时刻到达了同一个点)。答案对 \(10^4\) 取模。
注意到 \(2,3,4\) 的最小公倍数是 \(12\),那么考虑统一周期,全弄成长度为 \(12\) 的周期。
如果没有怪兽,那么这个题目非常板子了,直接矩乘秒解;如果有怪兽但是怪兽不会动,那么也是好解决的,只需要把转移矩阵修改一下——原先是一个普通的邻接矩阵,现在需要删除其中的一些边,因为你不能碰到怪兽所处的点。
但是现在的怪兽是会动的!这很烦,要是能把怪兽定住就好了。
为了之后的描述更加方便,这里先定义一下 DP 数组。很简单,就是 \(dp_i\) 表示从 \(St\) 出发有多少种路径能在恰好 \(G\) 步的情况下到达节点 \(i\)。\(G\) 会随着不断转移而变化,最初 \(G = 0\) 且只有 \(dp_{St}=1\)。并不需要记录 \(G\)。
首先考虑统一周期性,注意到 \(2,3,4\) 的最小公倍数是 \(12\),那么全弄成长度为 \(12\) 的周期就行。
那么,只要当前的时刻 $ t\bmod 12$ 同余,怪兽就会处在同一个位置不会移动。
因此可以考虑时刻 \(t \bmod 12\) 分别为 \(0 \sim 11\) 的 \(12\) 种情况,分别生成出一个邻接矩阵(定义为 \(H_i\),其中 \(i = t \bmod 12\))。
首先考虑结合在一起的情况。由于算出一个邻接矩阵,然后让它去和用来表示 DP 数组的矩阵相乘是可以实现转移的目的的,因此考虑弄一个 \(B\) 矩阵,并让它依次乘上 \(H_0 , H_1 , \dots , H_{11}\),这样它就是一个走 \(12\) 步的大矩阵了!那么我们可以把 \(K\) 中可以整除 \(12\) 的部分拿出来(即,计算满足 \(12k + p = K\) 且 \(p < 12\) 的 \(k\) 值),然后利用 \(B\) 走上个 \(k\) 步。也就是 \(B^k\)!
但是 \(K\) 不一定是 \(12\) 的倍数啊(转化为之前那个式子,就是说,\(p\) 不一定为 \(0\)),所以还需要用得到的答案 \(Ans\) 依次乘上 \(p\) 个矩阵 \(H_0 , H_1 , \dots , H_{p-1}\),才是最终的 DP 数组。
得到最终的 DP 数组了,那么直接输出下标索引为 \(Ed\) 的所对应的值就好了。
处理的时候要记得取模哦。
9.16
CF1102F Elongated Matrix
Tag:动态规划 DP,状压 DP,预处理。
犯的错误有:
- 搞混 \(x\) 和 \(n\)。
(1<<x)-1忘加括号以导致其变为1<<x-1。
下次使用变量名时想清楚含义。注意运算优先级。
CF1215E Marbles
Tag:动态规划 DP,状压 DP,预处理。
没犯什么错。
CF417D Cunning Gena
Tag:动态规划 DP,状压 DP,排序。
没犯什么错。
9.17
P1772 物流运输
Tag:最短路,单源最短路 Dijkstra,动态规划 DP,预处理。
没犯什么错。
题解。
定义 \(mny_{i,j}\) 表示第 \(i\) 天到第 \(j\) 天都走同一路线的情况下,每天需要多少花费。
嗯计算起来是很简单的,枚举 \(i\) 和 \(j\) 然后把 \(i \sim j\) 这些天中被封锁过的码头全设为【不可达】,然后跑最短路 Dijkstra 即可。
求出来之后就开搞 DP 啦!定义 \(dp_i\) 表示前 \(i\) 天的路线设计最少是多少。转移的时候枚举当前天 \(i\) 以及用于转移的 \(j\),转移式即为 \(dp_i = \min(dp_i,dp_j + mny_{j+1,i} \times (i-j) + k)\)。
显然最开始所有 \(dp_i = \infty\) 只有 \(dp_0 = 0\)。最终答案即为 \(dp_n\)。
P.S.最后的答案会比标准答案大 \(k\),初始化改为 \(dp_0 = -k\) 或最后改为输出 \(dp_n - k\) 均可。
P1608 路径统计
Tag:最短路,单源最短路 Dijkstra,记录路径边数。
犯的错误有:
- 将
cnt[v]=cnt[u]错误写成常规形式cnt[v]=1。
下次要想明白,赋初值或者别的东西的时候到底是要给哪个数值。
CF20C Dijkstra?
Tag:最短路,单源最短路 Dijkstra,记录路径。
没犯什么错。
CF229B Planets
Tag:最短路,单源最短路 Dijkstra。
没犯什么错。
9.19
P10234 B. 找机厅
Tag:广度优先搜索 BFS,记录路径。
犯的错误有:
- \(n\)、\(m\) 写反。
下次不要再变量写反了!!!!1111
AT_abc305_e Art Gallery on Graph
Tag:广度优先搜索 BFS,反向搜索(?是这么叫的吗)。
犯的错误有:
- 没有想清楚要用哪种 STL。
- 没有考虑特殊情况。
考虑清楚使用的变量的类型。记得考虑特殊情况!
CF1340C Nastya and Unexpected Guest
Tag:广度优先搜索 BFS,0/1 BFS。
犯的错误有:
- 没有在计算答案的时候 \(+1\) 导致答案一直为 \(1\)。
转移、更新的时候不要一成不变呐!
CF1749E Cactus Wall
Tag:思维转化,广度优先搜索 BFS,0/1 BFS,记录路径。
没犯什么错。
9.23
CF1245F Daniel and Spring Cleaning
Tag:数位 DP,统计方案数型数位 DP,简单转化。
这题做了这么久是个谜。主要是因为调了太久……
犯的错误有:
- 初始化错误,应为 \(dp_{0,1,1,1} = 1\) 而非 \(dp_{0,0,0,0} = 1\),因为最开始是顶格的。
- 数组开小,应至少要 \(30\) 的范围但我只开了 \(10\)。
注意初始化的时候要理解具体的含义再初始化。数组一定要注意开的范围,别开过大怕卡空间,也别开小会 RE/WA。
题解。
题目就是说,给定一个取值范围 \([l,r]\),问从中选择两个数 \(a\) 和 \(b\),有多少种情况下会满足 \(a \oplus b = a + b\)。考虑 \(a\) 和 \(b\) 的顺序,即 \((1,4)\) 和 \((4,1)\) 算作两种情况。
由于是位运算,首先考虑将其分解成二进制的情况。
同时处理两个数字的上下界显然是特别麻烦的,因此我们可以限制 \(a \le b\),最后将 \(ans \times 2\) 即可。
\(x = y\) 的时候是没有贡献的,因此不用担心,不过 \(a=b=0\) 是有贡献的所以当 \(l = 0\) 的时候得特判一下。
定义 \(dp_{i,x,y,p}\) 表示当前枚举到前 \(i\) 位,上下界标记分别为 \(x\) 和 \(y\),并且两个数是否相等(用 \(p\) 来表示,用处为确定 \(a\) 的具体取值范围)的情况下,存在多少种 \(a \oplus b = a + b\) 的情况。
转移是很简单的,根据 \(x,y,p\) 枚举出 \(a\) 这一位的值 \(v\) 以及 \(b\) 这一位的值 \(u\),然后对应可以算出 \(x_{new}\)、\(y_{new}\) 以及 \(p_{new}\),转移 \(dp_{i+1,x_{new},y_{new},p_{new}} = dp_{i+1,x_{new},y_{new},p_{new}} + dp_{i,x,y,p}\)。
算答案的时候枚举所有的 \(x,y,p\),然后累加 \(dp_{n,x,y,p}\) 即可。通俗的说,是 \(\sum_{x=0}^{1} \sum_{y=0}^{1} \sum_{p=0}^{1} dp_{n,x,y,p}\)。但是再说一遍,一定要注意特判 \(l=0\) 的情况哦,因为在这种情况下,将 \(ans \times 2\) 之后会把 \(a = b = 0\) 的情况多算一遍,一定要减去多余的那一遍哦!
AT_abc406_e Popcount Sum 3
Tag:数位 DP,计算数值具体总和型数位 DP。
比较板子的题目了,所以没犯什么错。
9.24
P5151 HKE与他的小朋友
Tag:倍增,置换。
没犯什么错,因为这是不要脑子的板子题。
CF1969A Two Friends
Tag:贪心,思维,置换。
为什么题单里会有红题呢,好不理解啊。
没犯什么错。红题还犯错就很可耻了!
CF1691B Shoe Shuffling
Tag:置换,双指针(?算吗),构造。
犯的错误有:
- 没有考虑 \(n=1\) 的特殊情况。
- 初始化不够完全,有的时候需要初始化辅助的一些位置,比如说 \(a_0\)、\(a_{n+1}\) 这种。此题就是一个 \(a_{n+1}\)。
一定要考虑题目的特殊情况,需要特判的就特判!初始化一定要看看,是不是要再多初始化一点,常数级别的时间复杂度是不影响的;换言之,多测的时候,所有被调用过的位置都得清空,除非确保那个位置从来没动用过。
CF1213C Book Reading
Tag:置换(这不算吧,只能说是类似),余数 / 同余。
没犯什么错。还有感觉这题和置换关系不大啊。
题解。
题目就是说,你要找出 \(1 \sim n\) 范围内的所有是 \(m\) 的倍数的数字的末位数字(即个位),然后将其累加起来并输出这个和。\(1 \le n,m \le 10^{16}\)。
这个数据范围显然是不能直接暴力的,因此要考虑一些别的做法。
容易发现至多循环 \(10\) 轮,这个末位数字就一定会回归。因为 \(10 \times m\) 不管加上任何数,个位数字都不会变。毕竟这数本来就以 \(0\) 结尾嘛,加上肯定不会变咯。
那么就可以想到去维护一个这样的循环节。不一定要维护 \(10 \times m\) 的循环节,可以视情况调整,比如说当 \(m=5\) 的时候两轮旧足够了。
这个东西可以 while 算出来。由于要具体的个位数字之和,那么要用 \(res\) 同时也记录一下一整个循环节的这个具体的答案值。并且 \(sum\) 是一定要有的,即这个循环节的大小,最大也只有 \(10 \times m\)。这一部分的时间复杂度只有 \(O(10)\),常数。
那么接下来可以考虑先求出完整循环节的情况,即,先让 \(Ans\) 的值加上 \(res \times \lfloor \frac{n}{sum} \rfloor\)。原因不多说,因为太显然。
但是不一定已经搞完了啊,所以肯定要继续。现在是已经解决掉了 \(\lfloor \frac{n}{sum} \rfloor \times sum\) 及之前的这一段,但后面还有一小段没搞定。
暴力?显然可以。因为这一段最多长度也就 \(sum\) 的样子,时间复杂度仍然 \(O(10)\)。嗯对没错,直接暴力模拟一下就 OK 了。
然后此题就结束了,代码短到可怜。
CF1249B2 Books Exchange (hard version)
Tag:置换,置换环。
没犯什么错。
最开始想复杂了,其实上解法可以更精简,但是无所谓了叭。
CF1283C Friends and Gifts
Tag:构造,STL 的运用(好冷门欸,大家用的是什么我不知道,至少我用的是 set)。
没犯什么错。
题解。
题目就是说,有 \(n\) 个人,其中第 \(i\) 个人决定把礼物送给 \(a_i\),如果 \(a_i = 0\) 代表他还没想好该送给谁。要求每个人必须送出恰好一份礼物,也必须受到恰好一份礼物,且不能自己给自己送礼物。现给出 \(n\) 和 \(a\) 的情况,请找出任意一种满足条件的方式。如有多种方案输出任意一种均可。
考虑提取出所有没收到礼物的人和所有没送出礼物的人的情况,显然这两种人的数量是相等的。
可以考虑建一个 set 存储所有暂时还没有送出任何礼物的人的集合。
然后遍历所有没收到礼物的人,去 set 里随便找一个人让他给现在遍历到的这个没收到礼物的人送礼物就好了,还要把他从 set 里删掉。注意别让自己给自己送礼物。
但是你会发现你过不去第一个样例,因为既没收到礼物也没送出礼物的人的选择余地会少一些,如果你在比较靠后的时间段去考虑他,他可能已经没有选择了,但他原来是有选择方案的,因此要先把既没收到礼物也没送出礼物的人处理完,再去处理只是梅收到礼物的人。
因此做两遍 for 就行了,第一次 for 处理既没收到礼物也没送出礼物的人,第二次 for 处理剩余的还没收到礼物的人。
时间复杂度 \(O(n \log n)\),有个 \(2\) 的常数。
CF1768D Lucky Permutation
Tag:置换,置换环,分裂置换环。
没犯什么错。
9.26
CF1401E Divide Square
Tag:扫描线,树状数组,问题转化。
没犯什么错。
题解。
题目就是说,在二维坐标轴上有一个左上角 \((0,0)\) 右下角 \((10^6,10^6)\) 的大矩阵,现在给定 \(n\) 条横线和 \(m\) 条竖线,问你经过这些线条的切割之后这个大矩阵被切割成了多少个块儿。
首先要考虑的是有哪些情况会产生一个块儿。
首先有正好横穿或者竖穿整个大方阵的情况;还有一种则是两条线相交的一个情况。
第一种是好维护的:判断一下这条边的范围是不是 \(0 \sim 10^6\) 的一个境界就 OK 了。需要考虑的是第二种。
想到把横线当成“修改”,把竖线弄成“询问”,这样一个形式,然后区间性地去找。
处理在横坐标为 \(0,1,2,3,\dots,10^6-2,10^6-1,10^6\) 的交点,可以把横坐标看成是时间节点。
在每一个时刻,先处理询问,再处理修改。
维护 \(s_i\) 表示当前时刻下纵坐标为 \(i\) 的位置是否被覆盖,用 \(0/1\) 表示。
然后可以定义两种函数,一是 \(\text{change}(t,u,k)\),表示在 \(t\) 时刻将 \(s_i\) 增加 \(u\);二是 \(\text{query}(t,l,r)\),表示询问在 \(t\) 时刻时,\(\sum_{i=l}^{r} s_i\) 的值是多少。
横边可以抽象为 \(\text{change}(l-1,x,1)\) 和 \(\text{change}(r,x,-1)\) 两步操作;竖边则可以理解为将 \(\text{query}(x,l,r)\) 的值加进 \(ans\) 里边;这是扫描线的常规思想,对吧。
然后把所有操作按照所谓时间排序即可。可以使用树状数组来维护,毕竟是一个标准的单点修改区间查询嘛。
为了实现方便,可以考虑最后偏移一下,对所有坐标值都 \(+1\),防止越界。
CF1139F Dish Shopping
Tag:离散化,扫描线,树状数组。
没犯什么错。
题解。
不得不承认这是一个特别赞的题目。
可以把其抽象化为一个三维偏序问题:
- \(p_i \le inc_j \le s_i\)
- \(b_i + p_i \le inc_j + pref_j\)
- \(b_i - p_i \ge pref_j - inc_j\)
然后把第一条限制拆一下变成四维偏序——显然可以跑 CDQ,据说还有方法可以压缩,弄成三条限制然后跑最普通的 CDQ——但是何必那么麻烦呢?
观察刚才想去拆解的第一条限制:\(p_i \le inc_j \le s_i\)。是一个范围限制——等会,这玩意儿,不是很像扫描线吗?
没错!这东西就是一个扫描线!
然后可以考虑画一个在二维平面上的图形,容易发现第一条限制就是点 \((inc_j,pref_j)\) 在直线 \(y = -x + b_i + p_i\) 上方,而第二条限制则是点 \((inc_j,pref_j)\) 在直线 \(y = x + b_i - p_i\),大概画出图来就是一个直角三角形,然后要求去维护一个点落在多少个三角形里。
这样就可以用普通的树状数组来维护了。然后这题就结束了。
由于数据范围有 \(10^9\) 所以一定要记得离散化哦。
AT_arc077_c guruguru
Tag:前缀和,二阶前缀和。
犯的错误有:
- 判断条件错将 \(l+2 \le r\) 写成 \(l+2 < r\)。
注意不要打错符号,注意判断条件的边界性问题。
9.29
P4875 Fair Photography G
Tag:Hash,前缀和,STL 运用(map)。
犯的错误有:
- 没有特判无解情况。
注意考虑题目特殊情况。
AT_abc339_f Product Equality
Tag:Hash,自然溢出 Hash,STL 运用(map),玄学。
没犯什么错。
AT_abc224_d 8 Puzzle on Graph
Tag:Hash,BFS,记忆化搜索。
犯的错误有:
- 没有新开 \(tmp\) 去拆解 Hash 值,导致破坏原值 \(now\),进一步导致后面的转移全部无效。
有的时候要注意某些东西是不能乱用的,如果需要可以考虑开临时变量克隆一份。
AT_abc284_f ABCBAC
Tag:Hash,前/后缀和。
犯的错误有:
- 数组没开两倍导致越界。
- 题目卡了自然溢出,需要加上取模操作才能过。
数组不要再开小了,该开几倍是几倍。注意 Hash 的用法,如果题目刻意去卡了某一种做法,就要赶紧换另一种做法。
9.30
CF282D Yet Another Number Game
Tag:博弈 DP,分类讨论。
没犯什么错。
题解。
题目就是说,有两个人 BitLGM 和 BitAryo 正在玩一个游戏,BitLGM 先手。每一个回合,当前玩家有两种选择方案,一种是指定一个 \(a_i\) 以及一个正整数 \(x\)(并且要保证 \(a_i \ge x\)),然后让 \(a_i\) 的值减去 \(x\);还有一种是指定一个正整数 \(x\) 并且要求 \(a\) 中所有数字都 \(\ge x\),然后让所有 \(a_i\) 的值都减去 \(x\)。轮到哪位玩家无法进行任何操作那么他就输了,对方就赢了。在两人都采取最优方案的情况下,请输出获胜者的名字。
第一眼看到这个题目感觉不太好维护,因为有 \(n\) 个数,一个个去维护具体情况会很复杂。再读题面发现 \(n \le 3\),太小了,这怎么搞都能过嘛!不过由于不总是 \(3\) 个数字
,可以分类讨论一下。
首先考虑 \(n=1\) 的情况,显然在这种情况下,如果 \(a_1 \ge 1\) 那么肯定先手赢,否则先手输。
接着考虑 \(n=2\) 的情况。
容易定义出状态 \(f_{x,y}\) ,表示当最开始的状态为 \({a_1} ^{\prime} = x\) 且 \({a_2} ^{\prime} = y\) 的情况下,先手是否有必胜策略。容易发现 \(f\) 数组是一个 bool 类型。
由于一旦有一个必胜策略,就不存在失败的情况了,所以我们可以考虑从必败情况转移到必胜情况。反过来等同于浪费时间,因为初始化的时候状态就是失败,没必要再额外转移了。
接下来考虑具体的转移。首先肯定要枚举 \(x\) 和 \(y\) 。然后我们可以考虑倒着推过去,这样会比较好处理。当然,上面也说了,得判断一下 \(f_{x,y}\) 是否为 \(0\) ,不为 \(0\) 的话 continue 掉就好了。还得枚举 \(p\),也就是题面中所说的 \(x\) ,即操作时减去的那个具体的数字。
然后就要看看能不能支持了。首先看只处理 \(a_1\),也就是只改变 \(x\) 的情况——只需要判断一下 \(x+p \le a_1\) 就可以了,这样就可以让 \(f_{x+p,y} = 1\) 了!同理,满足 \(y + p \le a_2\) 的话也有 \(f_{x,y+p} = 1\) 。
这样我们就处理完了选一个数改变的操作,但还有一个是全局改变。也不难,只要 \(x+p \le a_1\) 和 \(y + p \le a_2\) 这两个条件同时满足就可以了,那么就有 \(f_{x+p,y+p} = 1\) 了。
那么 DP 的部分就结束了。输出的时候判断一下,如果 \(f_{a_1 , a_2} = 1\) 就代表先手赢,否则代表后手赢。
\(n=3\) 的情况不讲,和 \(n=2\) 是一样的,多了一个维度 \(z\) 而已。
AT_abc349_e Weighted Tic-Tac-Toe
Tag:博弈 DP,DFS,记忆化搜索。
没犯什么错。
CF1866I Imagination Castle
Tag:博弈,递归,思维逻辑。
没犯什么错。
CF63E Sweets Game
Tag:博弈 DP,FS,记忆化搜索,二进制状态压缩。
没犯什么错。
10.10
CF1743F Intersection and Union
Tag:线段树,期望,乘法逆元。
没犯什么错。
CF718C Sasha and Array
Tag:线段树,矩阵,线段树维护矩阵,矩阵乘法,矩阵快速幂。
犯的错误有:
- 转移矩阵乘法的时候错误地将转移矩阵打成了单位矩阵。
- 判断函数错误地判断了“是否为全零矩阵”而非“是否为单位矩阵”。
- 矩阵乘法时没有对答案矩阵进行初始化,导致其可能遗留局部变量的随机数。
- 让
memset赋值了 \(1\)。
之后要注意写一行看一行,准确理解句子表达含义,确保无误。并且要注意写代码时要考虑逻辑的严谨,不能让代码有漏洞。还有,给我记住了,memset 只能赋值为 \(0,-1\) 和 0x3f,其他都不行,记好了!!!!!
10.14
CF120F Spiders
Tag:树的直径,贪心。
没犯什么错。
CF1325C Ehab and Path-etic MEXs
Tag:贪心,简单思维,分类讨论,\(\text{MEX}\)。
没犯什么错。
CF1406C Link Cut Centroids
Tag:树的重心,树的重心的性质。
没犯什么错。
CF1406C Link Cut Centroids
Tag:树的重心,树的重心的性质。
没犯什么错。
CF1805D A Wide, Wide Graph
Tag:树的直径,树的直径的性质及其运用。
没犯什么错。
CF639B Bear and Forgotten Tree 3
Tag:简单构造。
没犯什么错。
10.15
AT_abc220_e Distance on Large Perfect Binary Tree
Tag:二叉树,快速幂,思维,数学。
没犯什么错。
AT_nomura2020_c Folia
Tag:二叉树,二叉树的性质,构造,贪心。
没犯什么错。
题解。
题目就是说,给定 \(a_0 \sim a_n\),问是否存在一棵深度为 \(n\) 的二叉树,使得第 \(i\) 层拥有 \(a_i\) 个叶子结点。如果存在,请输出这棵二叉树最多能拥有多少个节点,否则输出 \(-1\)。
我们首先考虑判断这个东西是否有解。
什么时候才会无解呢?很显然,就是当某一行最多的节点数都拿出来了,还比这一行要求的叶子节点数少,就无解了。
但是这个东西该怎么算呢?考虑定义 \(p_i\) 表示第 \(i\) 层有多少个非叶子节点,最初有 \(p_0 = 1\),表示在根节点那一层有 \(1\) 个节点。
接着我们开始遍历所有 \(0 \sim n\) 的层。首先我们要更新当前的 \(p_i\)(\(i=0\) 的情况除外,因为已经赋初值为 \(1\) 了)——怎么更新呢?对极了,就是让 \(p_i = p_{i-1} \times 2\),不过要注意不要爆 long long。接着,我们要让 \(p_i \gets p_i - a_i\),这是干什么呢?当然是——把这一层的叶子节点减掉啦!当然,重头戏是判断,判断是否存在无解情况——对,就是判断 \(p_i\) 是否 \(<0\)!如果 \(p_i \ge 0\) 还好,如果 \(p_i < 0\) 的话,就无解了哦!想想看吧——如果让上一层的每个非叶子节点都有两个儿子节点,还不够这一层的叶子节点的话,那就肯定无解啦!
判断完无解,接下来该考虑如何算这个什么答案了。这一次,我们从下往上遍历——先遍历深的,再遍历浅的——也就是倒序遍历,从 \(n\) 到 \(0\)。
开个变量 \(now\) 表示这一层的节点个数——对,是节点个数,叶子节点也算啦!每个层遍历的时候统计下来,然后全都加进 \(Ans\) 里边。
具体该怎么做呢?首先,我们要保证这一层 \(now\) 的数值恰当——由于 \(now\) 是从下面一层继承过来的情况,但这一层可能根本没有这么多节点!因此一开始要让 \(now\) 对 \(p_i\) 取 \(\min\)。不过,这还只算了有儿子的非叶子节点,叶子节点当然也要计入统计啦!所以还要让 \(now\) 加上 \(a_i\)——上面两条合起来就是 \(now \gets \min(now,p_i) + a_i\) 啦!然后把这个 \(now\) 的值加进 \(Ans\) 里就可以咯。
话说回来,为什么 \(now\) 在数值不超的情况下,可以保留原值呢?显而易见,因为下一层有值,上面就有祖先——呸,有父亲。如果可以,肯定希望父亲尽可能多,可以让每位儿子都一一对应一个父亲,避免出现兄弟情况;但如果 \(p_i\) 不支持,怎么说这也是棵二叉树嘛,因此缩一下,让某些父亲有两个儿子就 OK 了。
这就是本题的全部做法啦,记得开 long long 哦。
CF1919D 01 Tree
Tag:构造,转化,思维,单调栈。
犯的错误有:
- 多测没有清空用来处理单调栈的
stack导致其预留了多余数值。
多测一定要记得清空,所有东西都要清空;有一个方法,就是把你所有定义的全局变量全部扫一遍,挨个看需不需要清空、有没有清空。
CF792D Paths in a Complete Binary Tree
Tag:简单推导,模拟。
没犯什么错。
10.17
P9013 Find and Replace S
Tag:图论建模,基环树,拓扑排序。
没犯什么错。
P8269 Visits S
Tag:简单思维,图论建模,找环,拓扑排序。
没犯什么错。
P3083 Luxury River Cruise S
Tag:图论,倍增,ST。
犯的错误有:
- 生成 ST 表的时候误用了普通 ST 表的枚举范围导致少算了很多东西。
之后要注意写代码的时候很多东西都是和板子不一样的,一定要注意区别,不要直接对着板子抄。
CF1968D Permutation Game
Tag:排列,排列的性质,图论建模,环,贪心。
没犯什么错。
AT_abc296_e Transition Game
Tag:思维,图论建模,拓扑排序。
没犯什么错。
AT_abc245_f Endless Walk
Tag:图论,反向建图,思维,拓扑排序。
没犯什么错。
10.18
P7299 Dance Mooves S
Tag:环,并查集,置换(?持怀疑态度)。
没犯什么错。
P6148 Swapity Swapity Swap S
Tag:暴力预处理,倍增,ST。
没犯什么错。
P3095 The Bessie Shuffle S
Tag:倒推,置换。
没犯什么错。
P1459三值的排序 Sorting a Three-Valued Sequence
Tag:排序,贪心,简单思维。
没犯什么错。
CF441D Valera and Swaps
Tag:找环,拆环 / 组合环,贪心。
没犯什么错。
题解。
首先题目定义了函数 \(f(a)\),表示最少需要多少次交换操作才能让长度为 \(n\) 的排列 \(a\) 变得从小到大。然后给定一个长度为 \(n\) 的排列 \(p\),问要执行多少次交换操作才能让 \(f(p) = m\),还要输出字典序最小的交换操作方案。即,如果交换的是 \(p_i\) 和 \(p_j\),那么在保证 \(i\) 最小的情况下还要保证 \(j\) 最小。
首先考虑这个 \(f\) 到底该咋算。
如果把按顺序来的排列数组和 \(p\) 数组的关系看作是一个连边关系的情况,也就是说说 \(i\) 连边到 \(p_i\),连成一个有向图,由于 \(p\) 是个排列,因此这个有向图一定是由一堆环组成的。
假设这里面有 \(k\) 个环,显而易见的,答案就是 \(n-k\)。因为一个环并不需要每条边都去操作,可以把它破成链,少去一次操作机会;那么有 \(k\) 个环,次数就是 \(n-k\) 啦。
那么我们就可以 \(O(n)\) 地求出一个排列所对应的 \(f\) 值了。
接着要来考虑具体的信息了。看看最初的 \(f(p)\) 与 \(m\) 的关系。
如果 \(f(p) < m\),那么就是要让 \(f(p)\) 增多,怎么增多?当然是让环的个数减少。反过来,如果 \(f(p) > m\),那么就是要让 \(f(p)\) 减少,怎么减少?那就是让环的个数增多啦!
于是现在就只需要考虑如何让 \(f(p)\) 增多或减少了。其实很简单的,如果要增多,就随便选一个由不止一个元素组成的环,然后从里面挑两个数 \(i\) 和 \(j\),然后交换一下 \(p_i\) 和 \(p_j\) 即可,这样,这个环就被劈半了。那么要减少怎么办?也很简单,随便选两个不在同一环内的数 \(i\) 和 \(j\),然后交换一下 \(p_i\) 和 \(p_j\) 即可,这样,这两个环就合并在一起啦!
一次操作只能增多或减少一个环,因此最少的次数就是 \(|f(p)-m|\)。
最后想想看,如何让字典序最小?增多的情况,就是先考虑最小的 \(i\),然后当 \(i\) 所处的这个环中有不止 \(i\) 一个元素的时候,就再从这个环里边找一个最小的 \(j\),连一下即可;减少的情况,可以直接考虑让 \(i=1\),然后去找最小的 \(j\),让它不和 \(1\) 待一个环里就行,连边即可。
每次操作完成后都要重新算一次 \(f(p)\),因为 \(p\) 改变了,\(f(p)\) 改变了,每个数对应的环也改变了。
这样的时间复杂度是 \(O(n^2)\) 的,\(n\) 只有 \(3000\),稳过。
具体求 \(f\) 那里,我记录了 \(bel_i\) 表示 \(i\) 所属的环的编号,每次枚举到一个 \(bel\) 为 \(0\) 的 \(i\),就新开一个编号弄给 \(bel_i\),并从 \(i\) 出发沿着 \(p_i\) 一直跳环把编号全部给定即可。
CF1677C Tokitsukaze and Two Colorful Tapes
Tag:推式子,并查集。
没犯什么错。
AT_abc241_a Digit Machine
Tag:置换。
为什么会有红题?
没犯什么错。
AT_abc350_c Sort
Tag:置换,贪心。
犯的错误有:
- 传参错误,没有考虑延迟性,或者已经交换了的情况。
写代码要注意先后顺序,大不了开个临时变量。
10.20
CF1311E Construct the Binary Tree
Tag:二叉树的性质,简单构造,贪心。
没犯什么错。
题解。
题目就是说,要求构造一棵有 \(n\) 个节点、根节点为 \(1\) 且所有节点的深度之和为 \(D\) 的二叉树,如果不存在输出 NO,否则输出 YES 以及除根节点外的所有节点的父节点编号。这里定义节点的深度表示为该节点到根节点的路径上经过了多少条边,即根节点的深度为 \(1\)。
首先考虑什么时候会无解:要么就是 \(D\) 太小了,考虑最小的构造方案这个深度之和都比 \(D\) 要大;要么就是 \(D\) 太大了,考虑最大的构造方案这个深度之和都够不到 \(D\)。
那么就考虑最小和最大的构造方案:最大的,显而易见,构造一条链,这样子深度之和就会是 \(0 + 1 + \dots + n-1\),即 \(\frac{n(n-1)}{2}\);最小的,那就是按照完全二叉树,或说是满二叉树的构造方法来构造,尽量把一层放满,再放下一层,这个东西可以用 \(dep\) 维护一下,每次让 \(dep_i = dep_{\lfloor \frac{i}{2} \rfloor} +1\),初始让 \(dep_1 = 0\),最后算 \(\sum_{i=1}^{n} dep_i\) 即可。
判断完这两个范围,就可以考虑具体构造了。
由于 \(n,D \le 5000\),很显然是支持平方级别的时间复杂度的,那么不妨暴力一些,先把最小的构造方法算出来,然后跑 while,只要这个深度之和达不到,每次就选一个深度的点,然后把它弄成下一个深度的点,具体需要判断一下是否可行。当然了,每次操作完就要让当前的这个深度之和 \(+1\),不然就死循环了。选择深度的时候,也可以考虑倒序,这样影响会小一些,成功概率也大一些,不会浪费过多时间。
这样子时间复杂度莫约是 \(O(n \times D)\) 的,没啥问题。
CF1294F Three Paths on a Tree
Tag:树的直径,树的直径的性质,树形 DP。
犯的错误有:
- 没有判断树为链的情况。
做题时一定要注意测一下边界情况,各种极端问题,最大最小,等。
CF1006E Military Problem
Tag:先序遍历,先序遍历的性质,树的性质。
没犯什么错。
AT_abc377_e Permute K times 2
Tag:图论建模,找环,快速幂。
没犯什么错。
AT_abc240_e Ranges on Tree
Tag:简单构造,贪心。
没犯什么错。
10.21
P10633 BZOJ2989 数列/BZOJ4170 极光
Tag:建模,曼哈顿距离转切比雪夫距离,离散化,CDQ 分治,三维偏序。
犯的错误有:
- 题目微微有些卡常,不要用
map离散化。
在题目时限不大的情况下尽量少用大常数算法,如 map,可以转化为排序去重这之类比 map 代码长一些但是运行效率高一些的方案。总之尽量少用大常数算法。
P2487 拦截导弹
Tag:四维偏序,CDQ 分治,CDQ 分治优化 DP。
没犯什么错。
P5621 德丽莎世界第一可爱
Tag:四维偏序,CDQ 分治,CDQ 分治优化 DP。
犯的错误有:
- 没有将 \(dp\) 以及 \(Ans\) 赋值为 \(- \infty\) 导致答案偏大。
- 没有将判断条件全部相同的元素按照贡献价值从大到小排序导致其乱序进一步导致贪心失效。
注意初值,有负数的情况要格外注意,实在不行都写全最好。注意很多时候的特殊情况,题目没有保证不存在全等的情况那就绝对有全等情况,注意特殊的判断,贪心要做到底。
AT_abc346_g Alone
Tag:线段树,扫描线,转化,建模。
没放什么错。
CF1070C Cloud Computing
Tag:扫描线,线段树。
没犯什么错。
P12144 地雷阵
Tag:解析几何,三角函数,区间覆盖,扫描线。
没犯什么错。

浙公网安备 33010602011771号